diff options
2964 files changed, 80324 insertions, 29428 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index a42adadcbeed..ab2b1109a27f 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -76,6 +76,7 @@ aconfig_declarations_group { "android.widget.flags-aconfig-java", "backstage_power_flags_lib", "backup_flags_lib", + "bluetooth_exported_flags_java_lib", "camera_platform_flags_core_java_lib", "com.android.hardware.input-aconfig-java", "com.android.input.flags-aconfig-java", @@ -532,6 +533,7 @@ aconfig_declarations { name: "android.content.pm.flags-aconfig", package: "android.content.pm", container: "system", + exportable: true, srcs: ["core/java/android/content/pm/flags.aconfig"], } @@ -542,6 +544,18 @@ java_aconfig_library { } java_aconfig_library { + name: "android.content.pm.flags-aconfig-java-export", + aconfig_declarations: "android.content.pm.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], + mode: "exported", + min_sdk_version: "30", + apex_available: [ + "//apex_available:platform", + "com.android.permission", + ], +} + +java_aconfig_library { name: "android.content.pm.flags-aconfig-java-host", aconfig_declarations: "android.content.pm.flags-aconfig", host_supported: true, diff --git a/Android.bp b/Android.bp index d6b303f62428..af312bf833e5 100644 --- a/Android.bp +++ b/Android.bp @@ -425,6 +425,7 @@ java_defaults { "sounddose-aidl-java", "modules-utils-expresslog", "perfetto_trace_javastream_protos_jarjar", + "libaconfig_java_proto_nano", ], } 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/DREAM_MANAGER_OWNERS b/DREAM_MANAGER_OWNERS new file mode 100644 index 000000000000..48bde6024cba --- /dev/null +++ b/DREAM_MANAGER_OWNERS @@ -0,0 +1 @@ +brycelee@google.com diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp index e7adf203334e..f6213b9cf983 100644 --- a/ProtoLibraries.bp +++ b/ProtoLibraries.bp @@ -31,6 +31,7 @@ gensrcs { "&& $(location soong_zip) -jar -o $(out) -C $(genDir)/$(in) -D $(genDir)/$(in)", srcs: [ + ":aconfigd_protos", ":ipconnectivity-proto-src", ":libstats_atom_enum_protos", ":libstats_atom_message_protos", diff --git a/Ravenwood.bp b/Ravenwood.bp index 3ab0934e9acc..412f2b746887 100644 --- a/Ravenwood.bp +++ b/Ravenwood.bp @@ -30,7 +30,7 @@ java_genrule { name: "framework-minus-apex.ravenwood-base", tools: ["hoststubgen"], cmd: "$(location hoststubgen) " + - "@$(location ravenwood/texts/ravenwood-standard-options.txt) " + + "@$(location :ravenwood-standard-options) " + "--debug-log $(location hoststubgen_framework-minus-apex.log) " + "--stats-file $(location hoststubgen_framework-minus-apex_stats.csv) " + @@ -42,13 +42,13 @@ java_genrule { "--gen-input-dump-file $(location hoststubgen_dump.txt) " + "--in-jar $(location :framework-minus-apex-for-hoststubgen) " + - "--policy-override-file $(location ravenwood/texts/framework-minus-apex-ravenwood-policies.txt) " + - "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ", + "--policy-override-file $(location :ravenwood-framework-policies) " + + "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ", srcs: [ ":framework-minus-apex-for-hoststubgen", - "ravenwood/texts/framework-minus-apex-ravenwood-policies.txt", - "ravenwood/texts/ravenwood-standard-options.txt", - "ravenwood/texts/ravenwood-annotation-allowed-classes.txt", + ":ravenwood-framework-policies", + ":ravenwood-standard-options", + ":ravenwood-annotation-allowed-classes", ], out: [ "ravenwood.jar", @@ -104,6 +104,18 @@ genrule { ], } +genrule { + name: "framework-minus-apex.ravenwood.keep_all", + defaults: ["ravenwood-internal-only-visibility-genrule"], + cmd: "cp $(in) $(out)", + srcs: [ + ":framework-minus-apex.ravenwood-base{hoststubgen_keep_all.txt}", + ], + out: [ + "hoststubgen_framework-minus-apex_keep_all.txt", + ], +} + java_library { name: "services.core-for-hoststubgen", installable: false, // host only jar. @@ -118,7 +130,7 @@ java_genrule { name: "services.core.ravenwood-base", tools: ["hoststubgen"], cmd: "$(location hoststubgen) " + - "@$(location ravenwood/texts/ravenwood-standard-options.txt) " + + "@$(location :ravenwood-standard-options) " + "--debug-log $(location hoststubgen_services.core.log) " + "--stats-file $(location hoststubgen_services.core_stats.csv) " + @@ -130,13 +142,13 @@ java_genrule { "--gen-input-dump-file $(location hoststubgen_dump.txt) " + "--in-jar $(location :services.core-for-hoststubgen) " + - "--policy-override-file $(location ravenwood/texts/services.core-ravenwood-policies.txt) " + - "--annotation-allowed-classes-file $(location ravenwood/texts/ravenwood-annotation-allowed-classes.txt) ", + "--policy-override-file $(location :ravenwood-services-policies) " + + "--annotation-allowed-classes-file $(location :ravenwood-annotation-allowed-classes) ", srcs: [ ":services.core-for-hoststubgen", - "ravenwood/texts/services.core-ravenwood-policies.txt", - "ravenwood/texts/ravenwood-standard-options.txt", - "ravenwood/texts/ravenwood-annotation-allowed-classes.txt", + ":ravenwood-services-policies", + ":ravenwood-standard-options", + ":ravenwood-annotation-allowed-classes", ], out: [ "ravenwood.jar", @@ -189,6 +201,18 @@ genrule { ], } +genrule { + name: "services.core.ravenwood.keep_all", + defaults: ["ravenwood-internal-only-visibility-genrule"], + cmd: "cp $(in) $(out)", + srcs: [ + ":services.core.ravenwood-base{hoststubgen_keep_all.txt}", + ], + out: [ + "hoststubgen_services.core_keep_all.txt", + ], +} + java_library { name: "services.core.ravenwood-jarjar", installable: false, @@ -246,12 +270,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 +299,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/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index c74c48ce6ba8..5f57c3973ab0 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -31,6 +31,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.app.Notification; import android.compat.Compatibility; import android.compat.annotation.ChangeId; @@ -1366,6 +1367,7 @@ public class JobInfo implements Parcelable { * @return This object for method chaining */ @FlaggedApi(Flags.FLAG_JOB_DEBUG_INFO_APIS) + @SuppressLint("BuilderSetStyle") @NonNull public Builder removeDebugTag(@NonNull String tag) { mDebugTags.remove(tag); 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/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java index 24d815f2964b..e73417460724 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java @@ -427,6 +427,12 @@ public class PowerExemptionManager { */ public static final int REASON_PACKAGE_UNARCHIVE = 328; + /** + * Tile onClick event + * @hide + */ + public static final int REASON_TILE_ONCLICK = 329; + /** @hide The app requests out-out. */ public static final int REASON_OPT_OUT_REQUESTED = 1000; @@ -504,13 +510,15 @@ public class PowerExemptionManager { REASON_ROLE_EMERGENCY, REASON_SYSTEM_MODULE, REASON_CARRIER_PRIVILEGED_APP, - REASON_OPT_OUT_REQUESTED, REASON_DPO_PROTECTED_APP, REASON_DISALLOW_APPS_CONTROL, REASON_ACTIVE_DEVICE_ADMIN, REASON_MEDIA_NOTIFICATION_TRANSFER, REASON_PACKAGE_INSTALLER, + REASON_SYSTEM_EXEMPT_APP_OP, REASON_PACKAGE_UNARCHIVE, + REASON_TILE_ONCLICK, + REASON_OPT_OUT_REQUESTED, }) @Retention(RetentionPolicy.SOURCE) public @interface ReasonCode {} diff --git a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java index 20da1718abb0..18ffb7ae70b1 100644 --- a/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java +++ b/apex/jobscheduler/framework/java/android/os/PowerWhitelistManager.java @@ -310,6 +310,11 @@ public class PowerWhitelistManager { * @hide */ public static final int REASON_SHELL = PowerExemptionManager.REASON_SHELL; + /** + * Tile onClick event + * @hide + */ + public static final int REASON_TILE_ONCLICK = PowerExemptionManager.REASON_TILE_ONCLICK; /** * The list of BG-FGS-Launch and temp-allowlist reason code. diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp index 06c7d64d1708..559490cec04f 100644 --- a/apex/jobscheduler/service/Android.bp +++ b/apex/jobscheduler/service/Android.bp @@ -33,6 +33,7 @@ java_library { "modules-utils-fastxmlserializer", "service-jobscheduler-alarm.flags-aconfig-java", "service-jobscheduler-job.flags-aconfig-java", + "service-jobscheduler-appidle.flags-aconfig-java", ], // Rename classes shared with the framework diff --git a/apex/jobscheduler/service/aconfig/Android.bp b/apex/jobscheduler/service/aconfig/Android.bp index 859c67ad8910..7b2525c67916 100644 --- a/apex/jobscheduler/service/aconfig/Android.bp +++ b/apex/jobscheduler/service/aconfig/Android.bp @@ -42,3 +42,16 @@ java_aconfig_library { name: "service-jobscheduler-alarm.flags-aconfig-java", aconfig_declarations: "alarm_flags", } + +// App Idle +aconfig_declarations { + name: "app_idle_flags", + package: "com.android.server.usage", + container: "system", + srcs: ["app_idle.aconfig"], +} + +java_aconfig_library { + name: "service-jobscheduler-appidle.flags-aconfig-java", + aconfig_declarations: "app_idle_flags", +} diff --git a/apex/jobscheduler/service/aconfig/app_idle.aconfig b/apex/jobscheduler/service/aconfig/app_idle.aconfig new file mode 100644 index 000000000000..c8976ca8361e --- /dev/null +++ b/apex/jobscheduler/service/aconfig/app_idle.aconfig @@ -0,0 +1,14 @@ +package: "com.android.server.usage" +container: "system" + +flag { + name: "avoid_idle_check" + namespace: "backstage_power" + description: "Postpone app idle check after boot completed" + is_fixed_read_only: true + bug: "337864590" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index e20f525fcdaf..e489c1ad891a 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -38,3 +38,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "thermal_restrictions_to_fgs_jobs" + namespace: "backstage_power" + description: "Apply thermal restrictions to FGS jobs." + bug: "315157163" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 3bb395f39123..ba8e3e8b48fc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1375,8 +1375,10 @@ class JobConcurrencyManager { final JobServiceContext jsc = mActiveServices.get(i); final JobStatus jobStatus = jsc.getRunningJobLocked(); - if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime() - && restriction.isJobRestricted(jobStatus)) { + if (jobStatus != null + && !jsc.isWithinExecutionGuaranteeTime() + && restriction.isJobRestricted( + jobStatus, mService.evaluateJobBiasLocked(jobStatus))) { jsc.cancelExecutingJobLocked(restriction.getStopReason(), restriction.getInternalReason(), JobParameters.getInternalReasonCodeDescription( diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 5d1433c815d6..ff73a4922977 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -80,6 +80,7 @@ import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.SystemClock; +import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; import android.os.storage.StorageManagerInternal; @@ -179,6 +180,8 @@ public class JobSchedulerService extends com.android.server.SystemService public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); public static final boolean DEBUG_STANDBY = DEBUG || false; + public static final String TRACE_TRACK_NAME = "JobScheduler"; + /** The maximum number of jobs that we allow an app to schedule */ private static final int MAX_JOBS_PER_APP = 150; /** The number of the most recently completed jobs to keep track of for debugging purposes. */ @@ -310,7 +313,8 @@ public class JobSchedulerService extends com.android.server.SystemService * Note: do not add to or remove from this list at runtime except in the constructor, because we * do not synchronize access to this list. */ - private final List<JobRestriction> mJobRestrictions; + @VisibleForTesting + final List<JobRestriction> mJobRestrictions; @GuardedBy("mLock") @VisibleForTesting @@ -3498,8 +3502,6 @@ public class JobSchedulerService extends com.android.server.SystemService /** * Check if a job is restricted by any of the declared {@link JobRestriction JobRestrictions}. - * Note, that the jobs with {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher may not - * be restricted, thus we won't even perform the check, but simply return null early. * * @param job to be checked * @return the first {@link JobRestriction} restricting the given job that has been found; null @@ -3508,13 +3510,9 @@ public class JobSchedulerService extends com.android.server.SystemService */ @GuardedBy("mLock") JobRestriction checkIfRestricted(JobStatus job) { - if (evaluateJobBiasLocked(job) >= JobInfo.BIAS_FOREGROUND_SERVICE) { - // Jobs with BIAS_FOREGROUND_SERVICE or higher should not be restricted - return null; - } for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { final JobRestriction restriction = mJobRestrictions.get(i); - if (restriction.isJobRestricted(job)) { + if (restriction.isJobRestricted(job, evaluateJobBiasLocked(job))) { return restriction; } } @@ -4221,6 +4219,7 @@ public class JobSchedulerService extends com.android.server.SystemService return curBias; } + /** Gets and returns the adjusted Job Bias **/ int evaluateJobBiasLocked(JobStatus job) { int bias = job.getBias(); if (bias >= JobInfo.BIAS_BOUND_FOREGROUND_SERVICE) { @@ -4348,7 +4347,11 @@ public class JobSchedulerService extends com.android.server.SystemService final boolean wasConsideredCharging = isConsideredCharging(); mChargingPolicy = newPolicy; - + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + "CHARGING POLICY CHANGED#" + mChargingPolicy); + } if (isConsideredCharging() != wasConsideredCharging) { for (int c = mControllers.size() - 1; c >= 0; --c) { mControllers.get(c).onBatteryStateChangedLocked(); @@ -5907,7 +5910,7 @@ public class JobSchedulerService extends com.android.server.SystemService if (isRestricted) { for (int i = mJobRestrictions.size() - 1; i >= 0; i--) { final JobRestriction restriction = mJobRestrictions.get(i); - if (restriction.isJobRestricted(job)) { + if (restriction.isJobRestricted(job, evaluateJobBiasLocked(job))) { final int reason = restriction.getInternalReason(); pw.print(" "); pw.print(JobParameters.getInternalReasonCodeDescription(reason)); @@ -6240,7 +6243,7 @@ public class JobSchedulerService extends com.android.server.SystemService proto.write(JobSchedulerServiceDumpProto.JobRestriction.REASON, restriction.getInternalReason()); proto.write(JobSchedulerServiceDumpProto.JobRestriction.IS_RESTRICTING, - restriction.isJobRestricted(job)); + restriction.isJobRestricted(job, evaluateJobBiasLocked(job))); proto.end(restrictionsToken); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index 39d50f53d2c4..d65a66c83fcc 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -535,29 +535,17 @@ public final class JobServiceContext implements ServiceConnection { sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount()); final String sourcePackage = job.getSourcePackageName(); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { - final String componentPackage = job.getServiceComponent().getPackageName(); - String traceTag = "*job*<" + job.getSourceUid() + ">" + sourcePackage; - if (!sourcePackage.equals(componentPackage)) { - traceTag += ":" + componentPackage; - } - traceTag += "/" + job.getServiceComponent().getShortClassName(); - if (!componentPackage.equals(job.serviceProcessName)) { - traceTag += "$" + job.serviceProcessName; - } - if (job.getNamespace() != null) { - traceTag += "@" + job.getNamespace(); - } - traceTag += "#" + job.getJobId(); - // Use the context's ID to distinguish traces since there'll only be one job // running per context. - Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", - traceTag, getId()); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, job.computeSystemTraceTag(), + getId()); } if (job.getAppTraceTag() != null) { // Use the job's ID to distinguish traces since the ID will be unique per app. - Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, "JobScheduler", - job.getAppTraceTag(), job.getJobId()); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, + JobSchedulerService.TRACE_TRACK_NAME, job.getAppTraceTag(), + job.getJobId()); } try { mBatteryStats.noteJobStart(job.getBatteryName(), job.getSourceUid()); @@ -1605,12 +1593,12 @@ public final class JobServiceContext implements ServiceConnection { completedJob.getFilteredTraceTag(), completedJob.getFilteredDebugTags()); if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler", - getId()); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, getId()); } if (completedJob.getAppTraceTag() != null) { - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, "JobScheduler", - completedJob.getJobId()); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, + JobSchedulerService.TRACE_TRACK_NAME, completedJob.getJobId()); } try { mBatteryStats.noteJobFinish(mRunningJob.getBatteryName(), mRunningJob.getSourceUid(), 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/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 7fca867356af..e3af1d894762 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -572,6 +572,9 @@ public final class JobStatus { /** The reason a job most recently went from ready to not ready. */ private int mReasonReadyToUnready = JobParameters.STOP_REASON_UNDEFINED; + /** The system trace tag for this job. */ + private String mSystemTraceTag; + /** * Core constructor for JobStatus instances. All other ctors funnel down to this one. * @@ -1058,6 +1061,38 @@ public final class JobStatus { return job.getTraceTag(); } + /** Returns a trace tag using debug information provided by job scheduler service. */ + @NonNull + public String computeSystemTraceTag() { + // Guarded by JobSchedulerService.mLock, no need for synchronization. + if (mSystemTraceTag != null) { + return mSystemTraceTag; + } + + mSystemTraceTag = computeSystemTraceTagInner(); + return mSystemTraceTag; + } + + @NonNull + private String computeSystemTraceTagInner() { + final String componentPackage = getServiceComponent().getPackageName(); + StringBuilder traceTag = new StringBuilder(128); + traceTag.append("*job*<").append(sourceUid).append(">").append(sourcePackageName); + if (!sourcePackageName.equals(componentPackage)) { + traceTag.append(":").append(componentPackage); + } + traceTag.append("/").append(getServiceComponent().getShortClassName()); + if (!componentPackage.equals(serviceProcessName)) { + traceTag.append("$").append(serviceProcessName); + } + if (mNamespace != null && !mNamespace.trim().isEmpty()) { + traceTag.append("@").append(mNamespace); + } + traceTag.append("#").append(getJobId()); + + return traceTag.toString(); + } + /** Returns whether this job was scheduled by one app on behalf of another. */ public boolean isProxyJob() { return mIsProxyJob; diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index c240b3f423a9..a1c72fb4c06c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -50,6 +50,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.ArraySet; @@ -2181,6 +2182,12 @@ public final class QuotaController extends StateController { } scheduleCutoff(); } + } else { + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + "QC/- " + mPkg); + } } } @@ -2720,6 +2727,11 @@ public final class QuotaController extends StateController { if (timeRemainingMs <= 50) { // Less than 50 milliseconds left. Start process of shutting down jobs. if (DEBUG) Slog.d(TAG, pkg + " has reached its quota."); + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + pkg + "#" + MSG_REACHED_TIME_QUOTA); + } mStateChangedListener.onControllerStateChanged( maybeUpdateConstraintForPkgLocked( sElapsedRealtimeClock.millis(), @@ -2748,6 +2760,11 @@ public final class QuotaController extends StateController { pkg.userId, pkg.packageName); if (timeRemainingMs <= 0) { if (DEBUG) Slog.d(TAG, pkg + " has reached its EJ quota."); + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + pkg + "#" + MSG_REACHED_EJ_TIME_QUOTA); + } mStateChangedListener.onControllerStateChanged( maybeUpdateConstraintForPkgLocked( sElapsedRealtimeClock.millis(), @@ -2772,6 +2789,12 @@ public final class QuotaController extends StateController { Slog.d(TAG, pkg + " has reached its count quota."); } + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + pkg + "#" + MSG_REACHED_COUNT_QUOTA); + } + mStateChangedListener.onControllerStateChanged( maybeUpdateConstraintForPkgLocked( sElapsedRealtimeClock.millis(), @@ -2928,6 +2951,11 @@ public final class QuotaController extends StateController { } mTempAllowlistGraceCache.delete(uid); mTopAppGraceCache.delete(uid); + if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) { + Trace.instantForTrack(Trace.TRACE_TAG_SYSTEM_SERVER, + JobSchedulerService.TRACE_TRACK_NAME, + "<" + uid + ">#" + MSG_END_GRACE_PERIOD); + } final ArraySet<String> packages = mService.getPackagesForUidLocked(uid); if (packages != null) { final int userId = UserHandle.getUserId(uid); diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java index 7aab67a00b1d..555a1186e0c9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java +++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java @@ -62,10 +62,11 @@ public abstract class JobRestriction { * fine with it). * * @param job to be checked + * @param bias job bias to be checked * @return false if the {@link JobSchedulerService} should not schedule this job at the moment, * true - otherwise */ - public abstract boolean isJobRestricted(JobStatus job); + public abstract boolean isJobRestricted(JobStatus job, int bias); /** Dump any internal constants the Restriction may have. */ public abstract void dumpConstants(IndentingPrintWriter pw); diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java index ef634b565b65..ba0111349bc9 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java +++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java @@ -24,6 +24,7 @@ import android.os.PowerManager.OnThermalStatusChangedListener; import android.util.IndentingPrintWriter; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.job.Flags; import com.android.server.job.JobSchedulerService; import com.android.server.job.controllers.JobStatus; @@ -85,7 +86,18 @@ public class ThermalStatusRestriction extends JobRestriction { } @Override - public boolean isJobRestricted(JobStatus job) { + public boolean isJobRestricted(JobStatus job, int bias) { + if (Flags.thermalRestrictionsToFgsJobs()) { + if (bias >= JobInfo.BIAS_TOP_APP) { + // Jobs with BIAS_TOP_APP should not be restricted + return false; + } + } else { + if (bias >= JobInfo.BIAS_FOREGROUND_SERVICE) { + // Jobs with BIAS_FOREGROUND_SERVICE or higher should not be restricted + return false; + } + } if (mThermalStatus >= UPPER_THRESHOLD) { return true; } @@ -107,6 +119,17 @@ public class ThermalStatusRestriction extends JobRestriction { || (mService.isCurrentlyRunningLocked(job) && mService.isJobInOvertimeLocked(job)); } + if (Flags.thermalRestrictionsToFgsJobs()) { + // Only let foreground jobs run if: + // 1. They haven't previously run + // 2. They're already running and aren't yet in overtime + if (bias >= JobInfo.BIAS_FOREGROUND_SERVICE + && job.getJob().isImportantWhileForeground()) { + return job.getNumPreviousAttempts() > 0 + || (mService.isCurrentlyRunningLocked(job) + && mService.isJobInOvertimeLocked(job)); + } + } if (priority == JobInfo.PRIORITY_HIGH) { return !mService.isCurrentlyRunningLocked(job) || mService.isJobInOvertimeLocked(job); @@ -114,6 +137,13 @@ public class ThermalStatusRestriction extends JobRestriction { return true; } if (mThermalStatus >= LOW_PRIORITY_THRESHOLD) { + if (Flags.thermalRestrictionsToFgsJobs()) { + if (bias >= JobInfo.BIAS_FOREGROUND_SERVICE) { + // No restrictions on foreground jobs + // on LOW_PRIORITY_THRESHOLD and below + return false; + } + } // For light throttling, throttle all min priority jobs and all low priority jobs that // aren't already running or have been running for long enough. return priority == JobInfo.PRIORITY_MIN diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index 4d4e3407a3c3..6265d9bb815f 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -788,7 +788,13 @@ public class AppIdleHistory { } appUsageHistory.nextEstimatedLaunchTime = getLongValue(parser, ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 0); - appUsageHistory.lastInformedBucket = -1; + if (Flags.avoidIdleCheck()) { + // Set lastInformedBucket to the same value with the currentBucket + // it should have already been informed. + appUsageHistory.lastInformedBucket = appUsageHistory.currentBucket; + } else { + appUsageHistory.lastInformedBucket = -1; + } userHistory.put(packageName, appUsageHistory); if (version >= XML_VERSION_ADD_BUCKET_EXPIRY_TIMES) { diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 410074e6ec85..c3fe0314636e 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -707,7 +707,7 @@ public class AppStandbyController initializeDefaultsForSystemApps(UserHandle.USER_SYSTEM); } - if (mPendingOneTimeCheckIdleStates) { + if (!Flags.avoidIdleCheck() && mPendingOneTimeCheckIdleStates) { postOneTimeCheckIdleStates(); } @@ -1021,7 +1021,7 @@ public class AppStandbyController == REASON_SUB_DEFAULT_APP_RESTORED)) { newBucket = getBucketForLocked(packageName, userId, elapsedRealtime); if (DEBUG) { - Slog.d(TAG, "Evaluated AOSP newBucket = " + Slog.d(TAG, "Evaluated " + packageName + " newBucket = " + standbyBucketToString(newBucket)); } reason = REASON_MAIN_TIMEOUT; @@ -1990,7 +1990,9 @@ public class AppStandbyController } } if (android.app.admin.flags.Flags.disallowUserControlBgUsageFix()) { - postCheckIdleStates(userId); + if (!Flags.avoidIdleCheck()) { + postCheckIdleStates(userId); + } } } @@ -2392,9 +2394,14 @@ public class AppStandbyController final boolean isHeadLess = !systemLauncherActivities.contains(pkg); if (updateHeadlessSystemAppCache(pkg, isHeadLess)) { - mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, - UserHandle.USER_SYSTEM, -1, pkg) - .sendToTarget(); + if (!Flags.avoidIdleCheck()) { + // Checking idle state for the each individual headless system app + // during the boot up is not necessary, a full idle check for all + // usres will be scheduled after boot completed. + mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, + UserHandle.USER_SYSTEM, -1, pkg) + .sendToTarget(); + } } } final long end = SystemClock.uptimeMillis(); @@ -2438,6 +2445,11 @@ public class AppStandbyController @Override public void dumpState(String[] args, PrintWriter pw) { + pw.println("Flags: "); + pw.println(" " + Flags.FLAG_AVOID_IDLE_CHECK + + ": " + Flags.avoidIdleCheck()); + pw.println(); + synchronized (mCarrierPrivilegedLock) { pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps + "): " + mCarrierPrivilegedApps); diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 28efa1efc683..6cfd2e0ce833 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -44,13 +44,13 @@ non_updatable_exportable_droidstubs { removed_api_file: ":non-updatable-removed.txt", }, last_released: { - api_file: ":android-non-updatable.api.public.latest", - removed_api_file: ":android-non-updatable-removed.api.public.latest", + api_file: ":android-non-updatable.api.combined.public.latest", + removed_api_file: ":android-non-updatable-removed.api.combined.public.latest", baseline_file: ":android-non-updatable-incompatibilities.api.public.latest", }, api_lint: { enabled: true, - new_since: ":android.api.public.latest", + new_since: ":android.api.combined.public.latest", baseline_file: ":non-updatable-lint-baseline.txt", }, }, @@ -124,13 +124,13 @@ non_updatable_exportable_droidstubs { removed_api_file: ":non-updatable-system-removed.txt", }, last_released: { - api_file: ":android-non-updatable.api.system.latest", - removed_api_file: ":android-non-updatable-removed.api.system.latest", + api_file: ":android-non-updatable.api.combined.system.latest", + removed_api_file: ":android-non-updatable-removed.api.combined.system.latest", baseline_file: ":android-non-updatable-incompatibilities.api.system.latest", }, api_lint: { enabled: true, - new_since: ":android.api.system.latest", + new_since: ":android.api.combined.system.latest", baseline_file: ":non-updatable-system-lint-baseline.txt", }, }, @@ -185,7 +185,7 @@ non_updatable_exportable_droidstubs { }, api_lint: { enabled: true, - new_since: ":android.api.test.latest", + new_since: ":android.api.combined.test.latest", baseline_file: ":non-updatable-test-lint-baseline.txt", }, }, @@ -263,13 +263,13 @@ non_updatable_exportable_droidstubs { removed_api_file: ":non-updatable-module-lib-removed.txt", }, last_released: { - api_file: ":android-non-updatable.api.module-lib.latest", - removed_api_file: ":android-non-updatable-removed.api.module-lib.latest", + api_file: ":android-non-updatable.api.combined.module-lib.latest", + removed_api_file: ":android-non-updatable-removed.api.combined.module-lib.latest", baseline_file: ":android-non-updatable-incompatibilities.api.module-lib.latest", }, api_lint: { enabled: true, - new_since: ":android.api.module-lib.latest", + new_since: ":android.api.combined.module-lib.latest", baseline_file: ":non-updatable-module-lib-lint-baseline.txt", }, }, @@ -1223,6 +1223,32 @@ droidstubs { api_levels_sdk_type: "module-lib", } +// Create a single jar containing the whole module-lib API surface. +// This is needed because Metalava only consumes the first jar file it is given +// and ignores the rest. +java_library { + name: "android_module_stubs_complete_current_with_test_libs", + static_libs: [ + "android_module_stubs_current_with_test_libs", + "framework-updatable-stubs-module_libs_api-exportable", + ], + defaults: ["android.jar_defaults"], + visibility: [ + "//visibility:override", + "//visibility:private", + ], +} + +// Produces an `api-versions.xml` file that includes up-to-date information +// about all the module-lib APIs, both updatable and non-updatable and historic +// information about all previous dessert and SDK extension releases. +droidstubs { + name: "api_versions_module_lib_complete", + defaults: ["api_versions_complete_defaults"], + srcs: [":android_module_stubs_complete_current_with_test_libs{.jar}"], + api_levels_sdk_type: "module-lib", +} + // Produces an `api-versions.xml` file that includes up-to-date information // about only the non-updatable system-server APIs and historic information // about all previous dessert and SDK extension releases. That historic @@ -1235,6 +1261,36 @@ droidstubs { api_levels_sdk_type: "system-server", } +// Create a single jar containing the whole system-server API surface. +// This is needed because Metalava only consumes the first jar file it is given +// and ignores the rest. +java_library { + name: "android_system_server_stubs_complete_current_with_test_libs", + static_libs: [ + "android_system_server_stubs_current_with_test_libs", + // system-server extends module-lib but libraries which only service-* + // libraries provided system-server APIs, so include module-lib APIs for + // the others, e.g. framework-* libraries. + "framework-updatable-stubs-module_libs_api-exportable", + "framework-updatable-stubs-system_server_api-exportable", + ], + defaults: ["android.jar_defaults"], + visibility: [ + "//visibility:override", + "//visibility:private", + ], +} + +// Produces an `api-versions.xml` file that includes up-to-date information +// about all the system-server APIs, both updatable and non-updatable and +// historic information about all previous dessert and SDK extension releases. +droidstubs { + name: "api_versions_system_server_complete", + defaults: ["api_versions_complete_defaults"], + srcs: [":android_system_server_stubs_complete_current_with_test_libs{.jar}"], + api_levels_sdk_type: "system-server", +} + ///////////////////////////////////////////////////////////////////// // hwbinder.stubs provides APIs required for building HIDL Java // libraries. diff --git a/api/api.go b/api/api.go index b31a26c90789..449fac63f90c 100644 --- a/api/api.go +++ b/api/api.go @@ -386,6 +386,26 @@ func createMergedFrameworkModuleLibStubs(ctx android.LoadHookContext, modules [] ctx.CreateModule(java.LibraryFactory, &props) } +func createMergedFrameworkSystemServerExportableStubs(ctx android.LoadHookContext, bootclasspath, system_server_classpath []string) { + // The user of this module compiles against the "core" SDK and against non-updatable bootclasspathModules, + // so remove to avoid dupes. + bootclasspathModules := removeAll(bootclasspath, core_libraries_modules) + bootclasspathModules = removeAll(bootclasspath, non_updatable_modules) + modules := append( + // Include all the module-lib APIs from the bootclasspath libraries. + transformArray(bootclasspathModules, "", ".stubs.exportable.module_lib"), + // Then add all the system-server APIs from the service-* libraries. + transformArray(system_server_classpath, "", ".stubs.exportable.system_server")..., + ) + props := libraryProps{} + props.Name = proptools.StringPtr("framework-updatable-stubs-system_server_api-exportable") + props.Static_libs = modules + props.Sdk_version = proptools.StringPtr("system_server_current") + props.Visibility = []string{"//frameworks/base"} + props.Is_stubs_module = proptools.BoolPtr(true) + ctx.CreateModule(java.LibraryFactory, &props) +} + func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules []string) { props := fgProps{} props.Name = proptools.StringPtr("all-modules-public-stubs-source") @@ -531,6 +551,7 @@ func (a *CombinedApis) createInternalModules(ctx android.LoadHookContext) { createMergedSystemExportableStubs(ctx, bootclasspath) createMergedTestExportableStubsForNonUpdatableModules(ctx) createMergedFrameworkModuleLibExportableStubs(ctx, bootclasspath) + createMergedFrameworkSystemServerExportableStubs(ctx, bootclasspath, system_server_classpath) createMergedAnnotationsFilegroups(ctx, bootclasspath, system_server_classpath) 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/cmds/bootanimation/FORMAT.md b/cmds/bootanimation/FORMAT.md index 01e8fe13fdf6..da8331af1492 100644 --- a/cmds/bootanimation/FORMAT.md +++ b/cmds/bootanimation/FORMAT.md @@ -126,7 +126,7 @@ the system property `service.bootanim.exit` to a nonzero string.) Use `zopflipng` if you have it, otherwise `pngcrush` will do. e.g.: for fn in *.png ; do - zopflipng -m ${fn}s ${fn}s.new && mv -f ${fn}s.new ${fn} + zopflipng -m ${fn} ${fn}.new && mv -f ${fn}.new ${fn} # or: pngcrush -q .... done diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java index 488292d68620..f726361effd6 100644 --- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java +++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java @@ -292,13 +292,17 @@ public class AccessibilityNodeInfoDumper { int childCount = node.getChildCount(); for (int x = 0; x < childCount; x++) { AccessibilityNodeInfo childNode = node.getChild(x); - + if (childNode == null) { + continue; + } if (!safeCharSeqToString(childNode.getContentDescription()).isEmpty() - || !safeCharSeqToString(childNode.getText()).isEmpty()) + || !safeCharSeqToString(childNode.getText()).isEmpty()) { return true; + } - if (childNafCheck(childNode)) + if (childNafCheck(childNode)) { return true; + } } return false; } diff --git a/config/Android.bp b/config/Android.bp index adce203e1140..c9948c31f1c3 100644 --- a/config/Android.bp +++ b/config/Android.bp @@ -33,7 +33,7 @@ prebuilt_etc { name: "preloaded-classes", src: "preloaded-classes", filename: "preloaded-classes", - installable: false, + no_full_install: true, } filegroup { diff --git a/core/api/current.txt b/core/api/current.txt index 53cf7d59f974..8a99e45af0ea 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3344,9 +3344,9 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); - method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public final void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); - method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") public final void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public boolean clearCache(); method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public final void disableSelf(); @@ -3354,7 +3354,7 @@ package android.accessibilityservice { method public android.view.accessibility.AccessibilityNodeInfo findFocus(int); method @NonNull public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController(); method @NonNull public final android.accessibilityservice.AccessibilityButtonController getAccessibilityButtonController(int); - method @FlaggedApi("android.view.accessibility.braille_display_hid") @NonNull public android.accessibilityservice.BrailleDisplayController getBrailleDisplayController(); + method @FlaggedApi("android.view.accessibility.braille_display_hid") @NonNull public final android.accessibilityservice.BrailleDisplayController getBrailleDisplayController(); method @NonNull @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public final android.accessibilityservice.FingerprintGestureController getFingerprintGestureController(); method @Nullable public final android.accessibilityservice.InputMethod getInputMethod(); method @NonNull public final android.accessibilityservice.AccessibilityService.MagnificationController getMagnificationController(); @@ -18087,6 +18087,11 @@ package android.graphics.text { public class LineBreaker { method @NonNull public android.graphics.text.LineBreaker.Result computeLineBreaks(@NonNull android.graphics.text.MeasuredText, @NonNull android.graphics.text.LineBreaker.ParagraphConstraints, @IntRange(from=0) int); + method @FlaggedApi("com.android.text.flags.missing_getter_apis") public int getBreakStrategy(); + method @FlaggedApi("com.android.text.flags.missing_getter_apis") public int getHyphenationFrequency(); + method @FlaggedApi("com.android.text.flags.missing_getter_apis") @Nullable public int[] getIndents(); + method @FlaggedApi("com.android.text.flags.missing_getter_apis") public int getJustificationMode(); + method @FlaggedApi("com.android.text.flags.missing_getter_apis") public boolean getUseBoundsForWidth(); field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2 field public static final int BREAK_STRATEGY_HIGH_QUALITY = 1; // 0x1 field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0 @@ -26592,7 +26597,7 @@ package android.media.session { method public long getFlags(); method @Nullable public android.media.MediaMetadata getMetadata(); method public String getPackageName(); - method @Nullable public android.media.session.MediaController.PlaybackInfo getPlaybackInfo(); + method @NonNull public android.media.session.MediaController.PlaybackInfo getPlaybackInfo(); method @Nullable public android.media.session.PlaybackState getPlaybackState(); method @Nullable public java.util.List<android.media.session.MediaSession.QueueItem> getQueue(); method @Nullable public CharSequence getQueueTitle(); @@ -26611,7 +26616,7 @@ package android.media.session { public abstract static class MediaController.Callback { ctor public MediaController.Callback(); - method public void onAudioInfoChanged(android.media.session.MediaController.PlaybackInfo); + method public void onAudioInfoChanged(@NonNull android.media.session.MediaController.PlaybackInfo); method public void onExtrasChanged(@Nullable android.os.Bundle); method public void onMetadataChanged(@Nullable android.media.MediaMetadata); method public void onPlaybackStateChanged(@Nullable android.media.session.PlaybackState); 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..14ae3f543436 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 } @@ -4261,6 +4260,12 @@ package android.window { field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR; } + public final class TaskFragmentParentInfo implements android.os.Parcelable { + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentParentInfo> CREATOR; + } + public final class TaskFragmentTransaction implements android.os.Parcelable { ctor public TaskFragmentTransaction(); method public void addChange(@Nullable android.window.TaskFragmentTransaction.Change); @@ -4285,8 +4290,8 @@ package android.window { method @Nullable public android.os.IBinder getActivityToken(); method @NonNull public android.os.Bundle getErrorBundle(); method @Nullable public android.os.IBinder getErrorCallbackToken(); - method @Nullable public android.content.res.Configuration getTaskConfiguration(); method @Nullable public android.window.TaskFragmentInfo getTaskFragmentInfo(); + method @Nullable public android.window.TaskFragmentParentInfo getTaskFragmentParentInfo(); method @Nullable public android.os.IBinder getTaskFragmentToken(); method public int getTaskId(); method public int getType(); @@ -4294,7 +4299,6 @@ package android.window { method @NonNull public android.window.TaskFragmentTransaction.Change setActivityToken(@NonNull android.os.IBinder); method @NonNull public android.window.TaskFragmentTransaction.Change setErrorBundle(@NonNull android.os.Bundle); method @NonNull public android.window.TaskFragmentTransaction.Change setErrorCallbackToken(@Nullable android.os.IBinder); - method @NonNull public android.window.TaskFragmentTransaction.Change setTaskConfiguration(@NonNull android.content.res.Configuration); method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentInfo(@NonNull android.window.TaskFragmentInfo); method @NonNull public android.window.TaskFragmentTransaction.Change setTaskFragmentToken(@NonNull android.os.IBinder); method @NonNull public android.window.TaskFragmentTransaction.Change setTaskId(int); 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/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index d70fa19a4468..fd9600c1f87f 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -3554,8 +3554,8 @@ public abstract class AccessibilityService extends Service { * @see #OVERLAY_RESULT_INVALID * @see #OVERLAY_RESULT_INTERNAL_ERROR */ - @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") - public void attachAccessibilityOverlayToDisplay( + @FlaggedApi(android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS) + public final void attachAccessibilityOverlayToDisplay( int displayId, @NonNull SurfaceControl sc, @NonNull @CallbackExecutor Executor executor, @@ -3627,8 +3627,8 @@ public abstract class AccessibilityService extends Service { * @see #OVERLAY_RESULT_INVALID * @see #OVERLAY_RESULT_INTERNAL_ERROR */ - @FlaggedApi("android.view.accessibility.a11y_overlay_callbacks") - public void attachAccessibilityOverlayToWindow( + @FlaggedApi(android.view.accessibility.Flags.FLAG_A11Y_OVERLAY_CALLBACKS) + public final void attachAccessibilityOverlayToWindow( int accessibilityWindowId, @NonNull SurfaceControl sc, @NonNull @CallbackExecutor Executor executor, @@ -3645,7 +3645,7 @@ public abstract class AccessibilityService extends Service { */ @FlaggedApi(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID) @NonNull - public BrailleDisplayController getBrailleDisplayController() { + public final BrailleDisplayController getBrailleDisplayController() { BrailleDisplayController.checkApiFlagIsEnabled(); synchronized (mLock) { if (mBrailleDisplayController == null) { diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index dc5c7f694c58..713d8e5dd12f 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -43,131 +43,189 @@ import android.window.ScreenCapture; */ interface IAccessibilityServiceConnection { + @RequiresNoPermission void setServiceInfo(in AccessibilityServiceInfo info); + @RequiresNoPermission void setAttributionTag(in String attributionTag); + @RequiresNoPermission String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId, long accessibilityNodeId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, long threadId, in Bundle arguments); + @RequiresNoPermission String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); + @RequiresNoPermission String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId, long accessibilityNodeId, String viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); + @RequiresNoPermission String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); + @RequiresNoPermission String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); + @RequiresNoPermission boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); + @RequiresNoPermission AccessibilityWindowInfo getWindow(int windowId); + @RequiresNoPermission AccessibilityWindowInfo.WindowListSparseArray getWindows(); + @RequiresNoPermission AccessibilityServiceInfo getServiceInfo(); + @RequiresNoPermission boolean performGlobalAction(int action); + + @RequiresNoPermission List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions(); + @RequiresNoPermission void disableSelf(); + @RequiresNoPermission oneway void setOnKeyEventResult(boolean handled, int sequence); + @RequiresNoPermission MagnificationConfig getMagnificationConfig(int displayId); + @RequiresNoPermission float getMagnificationScale(int displayId); + @RequiresNoPermission float getMagnificationCenterX(int displayId); + @RequiresNoPermission float getMagnificationCenterY(int displayId); + @RequiresNoPermission Region getMagnificationRegion(int displayId); + @RequiresNoPermission Region getCurrentMagnificationRegion(int displayId); + @RequiresNoPermission boolean resetMagnification(int displayId, boolean animate); + @RequiresNoPermission boolean resetCurrentMagnification(int displayId, boolean animate); + @RequiresNoPermission boolean setMagnificationConfig(int displayId, in MagnificationConfig config, boolean animate); + @RequiresNoPermission void setMagnificationCallbackEnabled(int displayId, boolean enabled); + @RequiresNoPermission boolean setSoftKeyboardShowMode(int showMode); + @RequiresNoPermission int getSoftKeyboardShowMode(); + @RequiresNoPermission void setSoftKeyboardCallbackEnabled(boolean enabled); - boolean switchToInputMethod(String imeId); + @RequiresNoPermission + boolean switchToInputMethod(String imeId); - int setInputMethodEnabled(String imeId, boolean enabled); + @RequiresNoPermission + int setInputMethodEnabled(String imeId, boolean enabled); + @RequiresNoPermission boolean isAccessibilityButtonAvailable(); + @RequiresNoPermission void sendGesture(int sequence, in ParceledListSlice gestureSteps); + @RequiresNoPermission void dispatchGesture(int sequence, in ParceledListSlice gestureSteps, int displayId); + @RequiresNoPermission boolean isFingerprintGestureDetectionAvailable(); + @RequiresNoPermission IBinder getOverlayWindowToken(int displayid); + @RequiresNoPermission int getWindowIdForLeashToken(IBinder token); + @RequiresNoPermission void takeScreenshot(int displayId, in RemoteCallback callback); + @RequiresNoPermission void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId, in ScreenCapture.ScreenCaptureListener listener, IAccessibilityInteractionConnectionCallback callback); + @RequiresNoPermission void setGestureDetectionPassthroughRegion(int displayId, in Region region); + @RequiresNoPermission void setTouchExplorationPassthroughRegion(int displayId, in Region region); + @RequiresNoPermission void setFocusAppearance(int strokeWidth, int color); + @RequiresNoPermission void setCacheEnabled(boolean enabled); + @RequiresNoPermission oneway void logTrace(long timestamp, String where, long loggingTypes, String callingParams, int processId, long threadId, int callingUid, in Bundle serializedCallingStackInBundle); + @RequiresNoPermission void setServiceDetectsGesturesEnabled(int displayId, boolean mode); + @RequiresNoPermission void requestTouchExploration(int displayId); + @RequiresNoPermission void requestDragging(int displayId, int pointerId); + @RequiresNoPermission void requestDelegating(int displayId); + @RequiresNoPermission void onDoubleTap(int displayId); + @RequiresNoPermission void onDoubleTapAndHold(int displayId); + @RequiresNoPermission void setAnimationScale(float scale); + @RequiresNoPermission void setInstalledAndEnabledServices(in List<AccessibilityServiceInfo> infos); - List<AccessibilityServiceInfo> getInstalledAndEnabledServices(); + @RequiresNoPermission + List<AccessibilityServiceInfo> getInstalledAndEnabledServices(); + + @RequiresNoPermission void attachAccessibilityOverlayToDisplay(int interactionId, int displayId, in SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback); + @RequiresNoPermission void attachAccessibilityOverlayToWindow(int interactionId, int accessibilityWindowId, in SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)") + @EnforcePermission("BLUETOOTH_CONNECT") void connectBluetoothBrailleDisplay(in String bluetoothAddress, in IBrailleDisplayController controller); + + @RequiresNoPermission void connectUsbBrailleDisplay(in UsbDevice usbDevice, in IBrailleDisplayController controller); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)") + @EnforcePermission("MANAGE_ACCESSIBILITY") void setTestBrailleDisplayData(in List<Bundle> brailleDisplays); }
\ No newline at end of file diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 7ee3413550af..497d47adc7cc 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -2015,7 +2015,7 @@ public class AccountManager { * null for no callback * @param handler {@link Handler} identifying the callback thread, * null for the main thread - * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated wether it + * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it * succeeded. * @hide */ diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index a5dd4a7207c3..79e2bd437c3e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -1183,6 +1183,7 @@ public class Activity extends ContextThemeWrapper * @see #setIntent(Intent, ComponentCaller) */ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + @SuppressLint("OnNameExpected") public @Nullable ComponentCaller getCaller() { return mCaller; } @@ -1203,6 +1204,7 @@ public class Activity extends ContextThemeWrapper * @see #getCaller */ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + @SuppressLint("OnNameExpected") public void setIntent(@Nullable Intent newIntent, @Nullable ComponentCaller newCaller) { internalSetIntent(newIntent, newCaller); } @@ -5796,6 +5798,8 @@ public class Activity extends ContextThemeWrapper * @see #onRequestPermissionsResult */ @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) + @SuppressLint("OnNameExpected") + // Suppress lint as this is an overload of the original API. public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) { final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager() : createDeviceContext(deviceId).getPackageManager(); @@ -7159,6 +7163,7 @@ public class Activity extends ContextThemeWrapper * @see ComponentCaller */ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + @SuppressLint("OnNameExpected") public @NonNull ComponentCaller getInitialCaller() { return mInitialCaller; } @@ -7186,10 +7191,11 @@ public class Activity extends ContextThemeWrapper * @see #getCaller */ @FlaggedApi(android.security.Flags.FLAG_CONTENT_URI_PERMISSION_APIS) + @SuppressLint("OnNameExpected") public @NonNull ComponentCaller getCurrentCaller() { if (mCurrentCaller == null) { throw new IllegalStateException("The caller is null because #getCurrentCaller should be" - + " called within #onNewIntent method"); + + " called within #onNewIntent or #onActivityResult methods"); } return mCurrentCaller; } @@ -9632,6 +9638,7 @@ public class Activity extends ContextThemeWrapper * the default behaviour */ @FlaggedApi(android.security.Flags.FLAG_ASM_RESTRICTIONS_ENABLED) + @SuppressLint("OnNameExpected") public void setAllowCrossUidActivitySwitchFromBelow(boolean allowed) { ActivityClient.getInstance().setAllowCrossUidActivitySwitchFromBelow(mToken, allowed); } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 5e9fdfbb6f6f..1e824a19193c 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -6257,6 +6257,29 @@ public class ActivityManager { * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the * app is being put in a power-save allowlist. + * <p> + * Example arguments when user force-stops an app from Settings: + * <pre> + * noteAppRestrictionEnabled( + * "com.example.app", + * appUid, + * RESTRICTION_LEVEL_FORCE_STOPPED, + * true, + * RESTRICTION_REASON_USER, + * "settings", + * 0); + * </pre> + * Example arguments when app is put in restricted standby bucket for exceeding X hours of jobs: + * <pre> + * noteAppRestrictionEnabled( + * "com.example.app", + * appUid, + * RESTRICTION_LEVEL_RESTRICTED_BUCKET, + * true, + * RESTRICTION_REASON_SYSTEM_HEALTH, + * "job_duration", + * X * 3600 * 1000L); + * </pre> * * @param packageName the package name of the app * @param uid the uid of the app @@ -6264,11 +6287,20 @@ public class ActivityManager { * @param enabled whether the state is being applied or removed * @param reason the reason for the restriction state change, from {@code RestrictionReason} * @param subReason a string sub reason limited to 16 characters that specifies additional - * information about the reason for restriction. + * information about the reason for restriction. This string must only contain + * reasons related to excessive system resource usage or in some cases, + * source of the restriction. This string must not contain any details that + * identify user behavior beyond their actions to restrict/unrestrict/launch + * apps in some way. + * Examples of system resource usage: wakelock, wakeups, mobile_data, + * binder_calls, memory, excessive_threads, excessive_cpu, gps_scans, etc. + * Examples of user actions: settings, notification, command_line, launch, etc. + * * @param threshold for reasons that are due to exceeding some threshold, the threshold value * must be specified. The unit of the threshold depends on the reason and/or * subReason. For time, use milliseconds. For memory, use KB. For count, use - * the actual count or normalized as per-hour. For power, use milliwatts. Etc. + * the actual count or if rate limited, normalized per-hour. For power, + * use milliwatts. For CPU, use mcycles. * * @hide */ diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index e66f7fe195e6..8e99e46be6ac 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -1270,4 +1270,27 @@ public abstract class ActivityManagerInternal { * @hide */ public abstract boolean shouldDelayHomeLaunch(int userId); + + /** + * Add a startup timestamp to the most recent start of the specified process. + * + * @param key The {@link ApplicationStartInfo} start timestamp key of the timestamp to add. + * @param timestampNs The clock monotonic timestamp to add in nanoseconds. + * @param uid The UID of the process to add this timestamp to. + * @param pid The process id of the process to add this timestamp to. + * @param userId The userId in the multi-user environment. + */ + public abstract void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid, + int userId); + + /** + * It is similar {@link IActivityManager#killApplication(String, int, int, String, int)} but + * it immediately stop the package. + * + * <p>Note: Do not call this method from inside PMS's lock, otherwise it'll run into + * watchdog reset. + * @hide + */ + public abstract void killApplicationSync(String pkgName, int appId, int userId, + String reason, int exitInfoReason); } diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java index 9ea55f5ff84c..c6a1546fb931 100644 --- a/core/java/android/app/ActivityOptions.java +++ b/core/java/android/app/ActivityOptions.java @@ -103,7 +103,8 @@ public class ActivityOptions extends ComponentOptions { @IntDef(prefix = {"MODE_BACKGROUND_ACTIVITY_START_"}, value = { MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED, MODE_BACKGROUND_ACTIVITY_START_ALLOWED, - MODE_BACKGROUND_ACTIVITY_START_DENIED}) + MODE_BACKGROUND_ACTIVITY_START_DENIED, + MODE_BACKGROUND_ACTIVITY_START_COMPAT}) public @interface BackgroundActivityStartMode {} /** * No explicit value chosen. The system will decide whether to grant privileges. @@ -117,6 +118,13 @@ public class ActivityOptions extends ComponentOptions { * Deny the {@link PendingIntent} to use the background activity start privileges. */ public static final int MODE_BACKGROUND_ACTIVITY_START_DENIED = 2; + /** + * Special behavior for compatibility. + * Similar to {@link #MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED} + * + * @hide + */ + public static final int MODE_BACKGROUND_ACTIVITY_START_COMPAT = -1; /** * The package name that created the options. diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java index be8f48df4aa6..c8ab260c8c4e 100644 --- a/core/java/android/app/ActivityTaskManager.java +++ b/core/java/android/app/ActivityTaskManager.java @@ -90,13 +90,6 @@ public class ActivityTaskManager { public static final int RESIZE_MODE_USER = RESIZE_MODE_PRESERVE_WINDOW; /** - * Input parameter to {@link IActivityTaskManager#resizeTask} used by window - * manager during a screen rotation. - * @hide - */ - public static final int RESIZE_MODE_SYSTEM_SCREEN_ROTATION = RESIZE_MODE_PRESERVE_WINDOW; - - /** * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates * that the resize should be performed even if the bounds appears unchanged. * @hide diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index caaaf519eaca..76c1ed619510 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -18,6 +18,7 @@ package android.app; import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN; import static android.app.ConfigurationController.createNewConfigAndUpdateIfNotNull; +import static android.app.Flags.skipBgMemTrimOnFgApp; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE; @@ -50,6 +51,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 +2238,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 +2271,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 @@ -7065,6 +7079,11 @@ public final class ActivityThread extends ClientTransactionHandler if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); try { + if (skipBgMemTrimOnFgApp() + && mLastProcessState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + && level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { + return; + } if (level >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) { PropertyInvalidatedCache.onTrimMemory(); } 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..6865f9c5013b 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"; } @@ -3573,6 +3583,9 @@ public class AppOpsManager { return mAttributionTag; } + /** + * Persistent device Id of the proxy that noted the op + */ @FlaggedApi(Flags.FLAG_DEVICE_ID_IN_OP_PROXY_INFO_ENABLED) public @Nullable String getDeviceId() { return mDeviceId; } diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java index 397477d72a9a..0e8e2e30c26f 100644 --- a/core/java/android/app/ComponentOptions.java +++ b/core/java/android/app/ComponentOptions.java @@ -18,6 +18,7 @@ package android.app; import static android.app.ActivityOptions.BackgroundActivityStartMode; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; @@ -54,7 +55,7 @@ public class ComponentOptions { public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION = "android.pendingIntent.backgroundActivityAllowedByPermission"; - private @Nullable Boolean mPendingIntentBalAllowed = null; + private Integer mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; private boolean mPendingIntentBalAllowedByPermission = false; ComponentOptions() { @@ -65,12 +66,9 @@ public class ComponentOptions { // results they want, which is their loss. opts.setDefusable(true); - boolean pendingIntentBalAllowedIsSetExplicitly = - opts.containsKey(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED); - if (pendingIntentBalAllowedIsSetExplicitly) { - mPendingIntentBalAllowed = - opts.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED); - } + mPendingIntentBalAllowed = + opts.getInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); setPendingIntentBackgroundActivityLaunchAllowedByPermission( opts.getBoolean( KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false)); @@ -85,7 +83,8 @@ public class ComponentOptions { * @hide */ @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean allowed) { - mPendingIntentBalAllowed = allowed; + mPendingIntentBalAllowed = allowed ? MODE_BACKGROUND_ACTIVITY_START_ALLOWED + : MODE_BACKGROUND_ACTIVITY_START_DENIED; } /** @@ -98,11 +97,8 @@ public class ComponentOptions { * @hide */ @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed() { - if (mPendingIntentBalAllowed == null) { - // cannot return null, so return the value used up to API level 33 for compatibility - return true; - } - return mPendingIntentBalAllowed; + // cannot return all detail, so return the value used up to API level 33 for compatibility + return mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_DENIED; } /** @@ -119,16 +115,15 @@ public class ComponentOptions { @BackgroundActivityStartMode int state) { switch (state) { case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: - mPendingIntentBalAllowed = null; - break; - case MODE_BACKGROUND_ACTIVITY_START_ALLOWED: - mPendingIntentBalAllowed = true; - break; case MODE_BACKGROUND_ACTIVITY_START_DENIED: - mPendingIntentBalAllowed = false; + case MODE_BACKGROUND_ACTIVITY_START_COMPAT: + case MODE_BACKGROUND_ACTIVITY_START_ALLOWED: + mPendingIntentBalAllowed = state; break; default: - throw new IllegalArgumentException(state + " is not valid"); + // Assume that future values are some variant of allowing the start. + mPendingIntentBalAllowed = MODE_BACKGROUND_ACTIVITY_START_ALLOWED; + break; } return this; } @@ -141,13 +136,7 @@ public class ComponentOptions { * @see #setPendingIntentBackgroundActivityStartMode(int) */ public @BackgroundActivityStartMode int getPendingIntentBackgroundActivityStartMode() { - if (mPendingIntentBalAllowed == null) { - return MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; - } else if (mPendingIntentBalAllowed) { - return MODE_BACKGROUND_ACTIVITY_START_ALLOWED; - } else { - return MODE_BACKGROUND_ACTIVITY_START_DENIED; - } + return mPendingIntentBalAllowed; } /** @@ -170,8 +159,8 @@ public class ComponentOptions { /** @hide */ public Bundle toBundle() { Bundle b = new Bundle(); - if (mPendingIntentBalAllowed != null) { - b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed); + if (mPendingIntentBalAllowed != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + b.putInt(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, mPendingIntentBalAllowed); } if (mPendingIntentBalAllowedByPermission) { b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 0e201384812d..cd7f1e4c02c2 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -679,11 +679,13 @@ public class Dialog implements DialogInterface, Window.Callback, if (keyCode == KeyEvent.KEYCODE_ESCAPE) { if (mCancelable) { cancel(); - } else { + event.startTracking(); + return true; + } else if (mWindow.shouldCloseOnTouchOutside()) { dismiss(); + event.startTracking(); + return true; } - event.startTracking(); - return true; } return false; diff --git a/core/java/android/app/DreamManager.java b/core/java/android/app/DreamManager.java index ef6982e4a13c..4ac40a1f77b2 100644 --- a/core/java/android/app/DreamManager.java +++ b/core/java/android/app/DreamManager.java @@ -18,6 +18,7 @@ package android.app; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import android.annotation.FlaggedApi; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; @@ -30,6 +31,7 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; import android.service.dreams.DreamService; +import android.service.dreams.Flags; import android.service.dreams.IDreamManager; /** @@ -217,4 +219,19 @@ public class DreamManager { } return false; } + + /** + * Sets whether the dream is obscured by something. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_DREAM_HANDLES_BEING_OBSCURED) + @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) + public void setDreamIsObscured(boolean isObscured) { + try { + mService.setDreamIsObscured(isObscured); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl index 3c6ff2865d04..f2228f94ff01 100644 --- a/core/java/android/app/ITaskStackListener.aidl +++ b/core/java/android/app/ITaskStackListener.aidl @@ -145,6 +145,11 @@ oneway interface ITaskStackListener { void onTaskSnapshotChanged(int taskId, in TaskSnapshot snapshot); /** + * Called when a task snapshot become invalidated. + */ + void onTaskSnapshotInvalidated(int taskId); + + /** * Reports that an Activity received a back key press when there were no additional activities * on the back stack. * diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index c3bac710f9be..d8f03b165a00 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) { @@ -5703,6 +5704,7 @@ public class Notification implements Parcelable p.headerless(resId == getBaseLayoutResource() || resId == getHeadsUpBaseLayoutResource() || resId == getCompactHeadsUpBaseLayoutResource() + || resId == getMessagingCompactHeadsUpLayoutResource() || resId == getMessagingLayoutResource() || resId == R.layout.notification_template_material_media); RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId); @@ -6490,6 +6492,12 @@ public class Notification implements Parcelable // visual regressions. @SuppressWarnings("AndroidFrameworkCompatChange") private boolean bigContentViewRequired() { + if (Flags.notificationExpansionOptional()) { + // Notifications without a bigContentView, style, or actions do not need to expand + boolean exempt = mN.bigContentView == null + && mStyle == null && mActions.size() == 0; + return !exempt; + } if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.S) { return true; } @@ -6593,12 +6601,12 @@ public class Notification implements Parcelable * @hide */ public RemoteViews createCompactHeadsUpContentView() { - // TODO(b/336225281): re-evaluate custom view usage. - if (useExistingRemoteView(mN.headsUpContentView)) { - return fullyCustomViewRequiresDecoration(false /* fromStyle */) - ? minimallyDecoratedHeadsUpContentView(mN.headsUpContentView) - : mN.headsUpContentView; - } else if (mStyle != null) { + // 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) { return styleView; @@ -6611,7 +6619,7 @@ public class Notification implements Parcelable // Notification text is shown as secondary header text // for the minimal hun when it is provided. // Time(when and chronometer) is not shown for the minimal hun. - p.headerTextSecondary(p.mText).text(null).hideTime(true); + p.headerTextSecondary(p.mText).text(null).hideTime(true).summaryText(""); return applyStandardTemplate( getCompactHeadsUpBaseLayoutResource(), p, @@ -7153,13 +7161,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()) { @@ -7298,6 +7300,10 @@ public class Notification implements Parcelable return R.layout.notification_template_material_compact_heads_up_base; } + private int getMessagingCompactHeadsUpLayoutResource() { + return R.layout.notification_template_material_messaging_compact_heads_up; + } + private int getBigBaseLayoutResource() { return R.layout.notification_template_material_big_base; } @@ -8886,6 +8892,62 @@ public class Notification implements Parcelable } } + private void fixTitleAndTextForCompactMessaging(StandardTemplateParams p) { + Message m = findLatestIncomingMessage(); + final CharSequence text = (m == null) ? null : m.mText; + CharSequence sender = m == null ? null + : m.mSender == null || TextUtils.isEmpty(m.mSender.getName()) + ? mUser.getName() : m.mSender.getName(); + + CharSequence conversationTitle = mIsGroupConversation ? mConversationTitle : null; + + // we want to have colon for possible title for conversation. + final BidiFormatter bidi = BidiFormatter.getInstance(); + if (sender != null) { + sender = mBuilder.mContext.getString( + com.android.internal.R.string.notification_messaging_title_template, + bidi.unicodeWrap(sender), ""); + } else if (conversationTitle != null) { + conversationTitle = mBuilder.mContext.getString( + com.android.internal.R.string.notification_messaging_title_template, + bidi.unicodeWrap(conversationTitle), ""); + } + + if (Flags.cleanUpSpansAndNewLines()) { + conversationTitle = stripStyling(conversationTitle); + sender = stripStyling(sender); + } + + final boolean showOnlySenderName = showOnlySenderName(); + + final CharSequence title; + boolean isConversationTitleAvailable = !showOnlySenderName && conversationTitle != null; + if (isConversationTitleAvailable) { + title = conversationTitle; + } else { + title = sender; + } + + p.title(title); + // when the conversation title is available, use headerTextSecondary for sender and + // summaryText for text + if (isConversationTitleAvailable) { + p.headerTextSecondary(sender); + p.summaryText(text); + } else { + // when it is not, use headerTextSecondary for text and don't use summaryText + p.headerTextSecondary(text); + p.summaryText(null); + } + } + + /** developer settings to always show sender name */ + private boolean showOnlySenderName() { + return SystemProperties.getBoolean( + "persist.compact_heads_up_notification.show_only_sender_name", + false); + } + /** * @hide */ @@ -9170,10 +9232,88 @@ public class Notification implements Parcelable @Nullable @Override public RemoteViews makeCompactHeadsUpContentView() { - // TODO(b/336229954): Apply minimal HUN treatment to Messaging Notifications. - return makeHeadsUpContentView(false); + final boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY; + Icon conversationIcon = null; + Notification.Action remoteInputAction = null; + if (isConversationLayout) { + + conversationIcon = mShortcutIcon; + + // conversation icon is m + // Extract the conversation icon for one to one conversations from + // the latest incoming message since + // fixTitleAndTextExtras also uses it as data source for title and text + if (conversationIcon == null && !mIsGroupConversation) { + final Message message = findLatestIncomingMessage(); + if (message != null) { + final Person sender = message.mSender; + if (sender != null) { + conversationIcon = sender.getIcon(); + } + } + } + + if (Flags.compactHeadsUpNotificationReply()) { + // Get the first non-contextual inline reply action. + final List<Notification.Action> nonContextualActions = + mBuilder.getNonContextualActions(); + for (int i = 0; i < nonContextualActions.size(); i++) { + final Notification.Action action = nonContextualActions.get(i); + if (mBuilder.hasValidRemoteInput(action)) { + remoteInputAction = action; + break; + } + } + } + } + + final StandardTemplateParams p = mBuilder.mParams.reset() + .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP) + .highlightExpander(isConversationLayout) + .fillTextsFrom(mBuilder) + .hideTime(true); + + fixTitleAndTextForCompactMessaging(p); + TemplateBindResult bindResult = new TemplateBindResult(); + + RemoteViews contentView = mBuilder.applyStandardTemplate( + mBuilder.getMessagingCompactHeadsUpLayoutResource(), p, bindResult); + contentView.setViewVisibility(R.id.header_text_secondary_divider, View.GONE); + contentView.setViewVisibility(R.id.header_text_divider, View.GONE); + if (conversationIcon != null) { + contentView.setViewVisibility(R.id.icon, View.GONE); + contentView.setViewVisibility(R.id.conversation_face_pile, View.GONE); + contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE); + contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true); + contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon); + } else if (mIsGroupConversation) { + contentView.setViewVisibility(R.id.icon, View.GONE); + contentView.setViewVisibility(R.id.conversation_icon, View.GONE); + contentView.setInt(R.id.status_bar_latest_event_content, + "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p)); + contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor", + mBuilder.getSmallIconColor(p)); + contentView.setBundle(R.id.status_bar_latest_event_content, "setGroupFacePile", + mBuilder.mN.extras); + } + + if (remoteInputAction != null) { + contentView.setViewVisibility(R.id.reply_action_container, View.VISIBLE); + + final RemoteViews inlineReplyButton = + mBuilder.generateActionButton(remoteInputAction, false, p); + // Clear the drawable + inlineReplyButton.setInt(R.id.action0, "setBackgroundResource", 0); + inlineReplyButton.setTextViewText(R.id.action0, + mBuilder.mContext.getString(R.string.notification_compact_heads_up_reply)); + contentView.addView(R.id.reply_action_container, inlineReplyButton); + } else { + contentView.setViewVisibility(R.id.reply_action_container, View.GONE); + } + return contentView; } + /** * @hide */ @@ -10355,7 +10495,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/NotificationManager.java b/core/java/android/app/NotificationManager.java index b82a1e3d8dfa..e4310c1a5361 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -2972,6 +2972,7 @@ public class NotificationManager { android.Manifest.permission.INTERACT_ACROSS_USERS, android.Manifest.permission.ACCESS_NOTIFICATIONS}) @FlaggedApi(android.service.notification.Flags.FLAG_CALLSTYLE_CALLBACK_API) + @SuppressLint("UserHandle") public void registerCallNotificationEventListener(@NonNull String packageName, @NonNull UserHandle userHandle, @NonNull @CallbackExecutor Executor executor, @NonNull CallNotificationEventListener listener) { diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index 97c2e43a1db6..2e38c06a7479 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -60,6 +60,9 @@ per-file ReceiverInfo* = file:/BROADCASTS_OWNERS # ComponentCaller per-file ComponentCaller.java = file:COMPONENT_CALLER_OWNERS +# DreamManager +per-file DreamManager.java = file:/DREAM_MANAGER_OWNERS + # GrammaticalInflectionManager per-file *GrammaticalInflection* = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS per-file grammatical_inflection_manager.aconfig = file:/services/core/java/com/android/server/grammaticalinflection/OWNERS 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/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index a29c196d88de..0deb842aff61 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -378,6 +378,14 @@ } ], "file_patterns": ["(/|^)ContextImpl.java"] + }, + { + "file_patterns": [ + "(/|^)Activity.*.java", + "(/|^)PendingIntent.java", + "(/|^)ComtextImpl.java" + ], + "name": "CtsWindowManagerBackgroundActivityTestCases" } ], "postsubmit": [ 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/TaskStackListener.java b/core/java/android/app/TaskStackListener.java index 0290cee94dc3..36f61fd3ef59 100644 --- a/core/java/android/app/TaskStackListener.java +++ b/core/java/android/app/TaskStackListener.java @@ -178,6 +178,9 @@ public abstract class TaskStackListener extends ITaskStackListener.Stub { } @Override + public void onTaskSnapshotInvalidated(int taskId) { } + + @Override public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) throws RemoteException { } diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig index 8c4667f15299..fa646a76768c 100644 --- a/core/java/android/app/activity_manager.aconfig +++ b/core/java/android/app/activity_manager.aconfig @@ -50,3 +50,34 @@ 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 + } +} + +flag { + namespace: "backstage_power" + name: "skip_bg_mem_trim_on_fg_app" + description: "Skip background memory trim event on foreground processes." + is_fixed_read_only: true + bug: "308927629" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 9ef8b38666c6..46c9e781bed1 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -21,6 +21,7 @@ import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_US import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; +import android.app.admin.flags.Flags; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Context; @@ -176,6 +177,10 @@ public final class DeviceAdminInfo implements Parcelable { * provisioned into "affiliated" mode when on a Headless System User Mode device. * * <p>This mode adds a Profile Owner to all users other than the user the Device Owner is on. + * + * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, + * DPCs should set the value of attribute "headless-device-owner-mode" inside the + * "headless-system-user" tag as "affiliated". */ public static final int HEADLESS_DEVICE_OWNER_MODE_AFFILIATED = 1; @@ -185,6 +190,10 @@ public final class DeviceAdminInfo implements Parcelable { * * <p>This mode only allows a single secondary user on the device blocking the creation of * additional secondary users. + * + * <p>Starting from Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, + * DPCs should set the value of attribute "headless-device-owner-mode" inside the + * "headless-system-user" tag as "single_user". */ @FlaggedApi(FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED) public static final int HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER = 2; @@ -383,17 +392,30 @@ public final class DeviceAdminInfo implements Parcelable { } mSupportsTransferOwnership = true; } else if (tagName.equals("headless-system-user")) { - String deviceOwnerModeStringValue = - parser.getAttributeValue(null, "device-owner-mode"); + String deviceOwnerModeStringValue = null; + if (Flags.headlessSingleUserCompatibilityFix()) { + deviceOwnerModeStringValue = parser.getAttributeValue( + null, "headless-device-owner-mode"); + } + if (deviceOwnerModeStringValue == null) { + deviceOwnerModeStringValue = + parser.getAttributeValue(null, "device-owner-mode"); + } - if (deviceOwnerModeStringValue.equalsIgnoreCase("unsupported")) { + if ("unsupported".equalsIgnoreCase(deviceOwnerModeStringValue)) { mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_UNSUPPORTED; - } else if (deviceOwnerModeStringValue.equalsIgnoreCase("affiliated")) { + } else if ("affiliated".equalsIgnoreCase(deviceOwnerModeStringValue)) { mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_AFFILIATED; - } else if (deviceOwnerModeStringValue.equalsIgnoreCase("single_user")) { + } else if ("single_user".equalsIgnoreCase(deviceOwnerModeStringValue)) { mHeadlessDeviceOwnerMode = HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; } else { - throw new XmlPullParserException("headless-system-user mode must be valid"); + if (Flags.headlessSingleUserCompatibilityFix()) { + Log.e(TAG, "Unknown headless-system-user mode: " + + deviceOwnerModeStringValue); + } else { + throw new XmlPullParserException( + "headless-system-user mode must be valid"); + } } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 69f29f3ab081..c529f7d95190 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1698,7 +1698,7 @@ public class DevicePolicyManager { /** * A boolean extra indicating whether device encryption can be skipped as part of - * <a href="#managed-provisioning>provisioning</a>. + * <a href="#managed-provisioning">provisioning</a>. * * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} or an intent with action * {@link #ACTION_PROVISION_MANAGED_DEVICE} that starts device owner provisioning. @@ -10427,7 +10427,7 @@ public class DevicePolicyManager { @WorkerThread public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName, Bundle settings) { - if (!Flags.dmrhCanSetAppRestriction()) { + if (!Flags.dmrhSetAppRestrictions()) { throwIfParentInstance("setApplicationRestrictions"); } @@ -11835,7 +11835,7 @@ public class DevicePolicyManager { @WorkerThread public @NonNull Bundle getApplicationRestrictions( @Nullable ComponentName admin, String packageName) { - if (!Flags.dmrhCanSetAppRestriction()) { + if (!Flags.dmrhSetAppRestrictions()) { throwIfParentInstance("getApplicationRestrictions"); } @@ -14120,7 +14120,7 @@ public class DevicePolicyManager { public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) { throwIfParentInstance("getParentProfileInstance"); try { - if (Flags.dmrhCanSetAppRestriction()) { + if (Flags.dmrhSetAppRestrictions()) { UserManager um = mContext.getSystemService(UserManager.class); if (!um.isManagedProfile()) { throw new SecurityException("The current user does not have a parent profile."); diff --git a/core/java/android/app/admin/StringSetPolicyValue.java b/core/java/android/app/admin/PackageSetPolicyValue.java index 12b11f4ba687..8b253a23a299 100644 --- a/core/java/android/app/admin/StringSetPolicyValue.java +++ b/core/java/android/app/admin/PackageSetPolicyValue.java @@ -28,18 +28,18 @@ import java.util.Set; /** * @hide */ -public final class StringSetPolicyValue extends PolicyValue<Set<String>> { +public final class PackageSetPolicyValue extends PolicyValue<Set<String>> { - public StringSetPolicyValue(@NonNull Set<String> value) { + public PackageSetPolicyValue(@NonNull Set<String> value) { super(value); if (Flags.devicePolicySizeTrackingInternalBugFixEnabled()) { - for (String str : value) { - PolicySizeVerifier.enforceMaxStringLength(str, "policyValue"); + for (String packageName : value) { + PolicySizeVerifier.enforceMaxPackageNameLength(packageName); } } } - public StringSetPolicyValue(Parcel source) { + public PackageSetPolicyValue(Parcel source) { this(readValues(source)); } @@ -56,7 +56,7 @@ public final class StringSetPolicyValue extends PolicyValue<Set<String>> { public boolean equals(@Nullable Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - StringSetPolicyValue other = (StringSetPolicyValue) o; + PackageSetPolicyValue other = (PackageSetPolicyValue) o; return Objects.equals(getValue(), other.getValue()); } @@ -67,7 +67,7 @@ public final class StringSetPolicyValue extends PolicyValue<Set<String>> { @Override public String toString() { - return "StringSetPolicyValue { " + getValue() + " }"; + return "PackageNameSetPolicyValue { " + getValue() + " }"; } @Override @@ -84,16 +84,16 @@ public final class StringSetPolicyValue extends PolicyValue<Set<String>> { } @NonNull - public static final Creator<StringSetPolicyValue> CREATOR = - new Creator<StringSetPolicyValue>() { + public static final Creator<PackageSetPolicyValue> CREATOR = + new Creator<PackageSetPolicyValue>() { @Override - public StringSetPolicyValue createFromParcel(Parcel source) { - return new StringSetPolicyValue(source); + public PackageSetPolicyValue createFromParcel(Parcel source) { + return new PackageSetPolicyValue(source); } @Override - public StringSetPolicyValue[] newArray(int size) { - return new StringSetPolicyValue[size]; + public PackageSetPolicyValue[] newArray(int size) { + return new PackageSetPolicyValue[size]; } }; } diff --git a/core/java/android/app/admin/SystemUpdatePolicy.java b/core/java/android/app/admin/SystemUpdatePolicy.java index 7320cea16451..dede5b5f48f3 100644 --- a/core/java/android/app/admin/SystemUpdatePolicy.java +++ b/core/java/android/app/admin/SystemUpdatePolicy.java @@ -78,6 +78,11 @@ import java.util.stream.Collectors; * * <h3>Developer guide</h3> * To learn more, read <a href="{@docRoot}work/dpc/system-updates">Manage system updates</a>. + * <p><strong>Note:</strong> <a href="https://source.android.com/docs/core/ota/modular-system"> + * Google Play system updates</a> (also called Mainline updates) are automatically downloaded + * but require a device reboot to be installed. Refer to the mainline section in + * <a href="{@docRoot}work/dpc/system-updates#mainline">Manage system + * updates</a> for further details.</p> * * @see DevicePolicyManager#setSystemUpdatePolicy * @see DevicePolicyManager#getSystemUpdatePolicy diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 18914e120d52..ad1ae9881a0c 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -115,6 +115,16 @@ flag { } flag { + name: "hsum_unlock_notification_fix" + namespace: "enterprise" + description: "Using the right userId when starting the work profile unlock flow " + bug: "327350831" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "dumpsys_policy_engine_migration_enabled" namespace: "enterprise" description: "Update DumpSys to include information about migrated APIs in DPE" @@ -217,6 +227,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" @@ -234,10 +254,13 @@ flag { } flag { - name: "dmrh_can_set_app_restriction" + name: "dmrh_set_app_restrictions" namespace: "enterprise" description: "Allow DMRH to set application restrictions (both on the profile and the parent)" bug: "328758346" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -303,3 +326,40 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "backup_connected_apps_settings" + namespace: "enterprise" + description: "backup and restore connected work and personal apps user settings across devices" + bug: "175067666" +} + +flag { + name: "headless_single_user_compatibility_fix" + namespace: "enterprise" + description: "Fix for compatibility issue introduced from using single_user mode on pre-Android V builds" + bug: "338050276" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "headless_single_min_target_sdk" + namespace: "enterprise" + description: "Only allow DPCs targeting Android V to provision into single user mode" + bug: "338588825" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "onboarding_consentless_bugreports" + namespace: "enterprise" + description: "Allow subsequent bugreports to skip user consent within a time frame" + bug: "340439309" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index e3c367f8adce..6ceae17d05fb 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -39,6 +39,13 @@ flag { } flag { + name: "check_autogroup_before_post" + namespace: "systemui" + description: "Does a check to see if notification should be autogrouped before posting, and if so groups before post." + bug: "330214226" +} + +flag { name: "visit_risky_uris" namespace: "systemui" description: "Guards the security fix that ensures all URIs in intents and Person.java are valid" @@ -53,6 +60,13 @@ flag { } flag { + name: "notification_expansion_optional" + namespace: "systemui" + description: "Experiment to restore the pre-S behavior where standard notifications are not expandable unless they have actions." + bug: "339523906" +} + +flag { name: "keyguard_private_notifications" namespace: "systemui" description: "Fixes the behavior of KeyguardManager#setPrivateNotificationsAllowed()" @@ -146,3 +160,10 @@ flag { description: "[Minimal HUN] Enables the compact heads up notification feature" bug: "270709257" } + +flag { + name: "compact_heads_up_notification_reply" + namespace: "systemui" + description: "[Minimal HUN] Enables the compact heads up notification reply capability for Conversation Notifications" + bug: "336229954" +}
\ No newline at end of file diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java index 5e1c1e053599..a37f51bb6944 100644 --- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java +++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java @@ -529,7 +529,6 @@ public final class OnDeviceIntelligenceManager { * {@link Bundle}s annotated with this type will be validated that they are in-effect read-only * when passed via Binder IPC. Following restrictions apply : * <ul> - * <li> No Nested Bundles are allowed.</li> * <li> {@link PersistableBundle}s are allowed.</li> * <li> Any primitive types or their collections can be added as usual.</li> * <li>IBinder objects should *not* be added.</li> diff --git a/core/java/android/app/prediction/AppPredictionContext.java b/core/java/android/app/prediction/AppPredictionContext.java index 99fa869cee93..1b718d436d6d 100644 --- a/core/java/android/app/prediction/AppPredictionContext.java +++ b/core/java/android/app/prediction/AppPredictionContext.java @@ -24,6 +24,8 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import java.util.Objects; + /** * Class that provides contextual information about the environment in which the app prediction is * used, such as package name, UI in which the app targets are shown, and number of targets. @@ -99,6 +101,13 @@ public final class AppPredictionContext implements Parcelable { } @Override + public int hashCode() { + int hashCode = Objects.hash(mUiSurface, mPackageName); + hashCode = 31 * hashCode + mPredictedTargetCount; + return hashCode; + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/app/prediction/AppTarget.java b/core/java/android/app/prediction/AppTarget.java index fef9e7020097..25c1a594ad81 100644 --- a/core/java/android/app/prediction/AppTarget.java +++ b/core/java/android/app/prediction/AppTarget.java @@ -167,6 +167,16 @@ public final class AppTarget implements Parcelable { } @Override + public int hashCode() { + int hashCode = Objects.hash(mId, mPackageName, mClassName, mUser); + if (mShortcutInfo != null) { + hashCode = 31 * hashCode + mShortcutInfo.getId().hashCode(); + } + hashCode = 31 * hashCode + mRank; + return hashCode; + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/app/prediction/AppTargetEvent.java b/core/java/android/app/prediction/AppTargetEvent.java index 91da8ec71dae..e36d87899b62 100644 --- a/core/java/android/app/prediction/AppTargetEvent.java +++ b/core/java/android/app/prediction/AppTargetEvent.java @@ -24,6 +24,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Objects; /** * A representation of an app target event. @@ -116,6 +117,13 @@ public final class AppTargetEvent implements Parcelable { } @Override + public int hashCode() { + int hashCode = Objects.hash(mTarget, mLocation); + hashCode = 31 * hashCode + mAction; + return hashCode; + } + + @Override public int describeContents() { return 0; } diff --git a/core/java/android/app/prediction/OWNERS b/core/java/android/app/prediction/OWNERS index fe012da8e307..73168fb90dbc 100644 --- a/core/java/android/app/prediction/OWNERS +++ b/core/java/android/app/prediction/OWNERS @@ -1,2 +1,4 @@ +pinyaoting@google.com +hyunyoungs@google.com adamcohen@google.com sunnygoyal@google.com diff --git a/core/java/android/app/servertransaction/ClientTransactionListenerController.java b/core/java/android/app/servertransaction/ClientTransactionListenerController.java index cda286742d28..9b53461568ca 100644 --- a/core/java/android/app/servertransaction/ClientTransactionListenerController.java +++ b/core/java/android/app/servertransaction/ClientTransactionListenerController.java @@ -33,11 +33,13 @@ import android.hardware.display.DisplayManagerGlobal; import android.os.IBinder; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Log; import android.window.ActivityWindowInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import java.util.concurrent.RejectedExecutionException; import java.util.function.BiConsumer; /** @@ -47,6 +49,8 @@ import java.util.function.BiConsumer; */ public class ClientTransactionListenerController { + private static final String TAG = "ClientTransactionListenerController"; + private static ClientTransactionListenerController sController; private final Object mLock = new Object(); @@ -179,10 +183,14 @@ public class ClientTransactionListenerController { } // Dispatch the display changed callbacks. - final int displayCount = configUpdatedDisplayIds.size(); - for (int i = 0; i < displayCount; i++) { - final int displayId = configUpdatedDisplayIds.valueAt(i); - onDisplayChanged(displayId); + try { + final int displayCount = configUpdatedDisplayIds.size(); + for (int i = 0; i < displayCount; i++) { + final int displayId = configUpdatedDisplayIds.valueAt(i); + onDisplayChanged(displayId); + } + } catch (RejectedExecutionException e) { + Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down"); } } @@ -222,7 +230,11 @@ public class ClientTransactionListenerController { } if (changedDisplayId != INVALID_DISPLAY) { - onDisplayChanged(changedDisplayId); + try { + onDisplayChanged(changedDisplayId); + } catch (RejectedExecutionException e) { + Log.w(TAG, "Failed to notify DisplayListener because the Handler is shutting down"); + } } } @@ -235,9 +247,11 @@ public class ClientTransactionListenerController { /** * Called when receives a {@link Configuration} changed event that is updating display-related * window configuration. + * + * @throws RejectedExecutionException if the display listener handler is closing. */ @VisibleForTesting - public void onDisplayChanged(int displayId) { + public void onDisplayChanged(int displayId) throws RejectedExecutionException { mDisplayManager.handleDisplayChangeFromWindowManager(displayId); } } diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig index c7b168aaf81d..04c36867271c 100644 --- a/core/java/android/app/usage/flags.aconfig +++ b/core/java/android/app/usage/flags.aconfig @@ -47,3 +47,14 @@ flag { description: "Feature flag for collecting app data size by file type API" bug: "294088945" } + +flag { + name: "disable_idle_check" + namespace: "backstage_power" + description: "disable idle check for USER_SYSTEM during boot up" + is_fixed_read_only: true + bug: "337864590" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 57b5c13a659d..3213b40b3437 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -1041,6 +1041,7 @@ public class AppWidgetManager { */ public AppWidgetProviderInfo getAppWidgetInfo(int appWidgetId) { if (mService == null) { + Log.e(TAG, "Service wasn't initialized, appWidgetId=" + appWidgetId); return null; } try { @@ -1048,6 +1049,9 @@ public class AppWidgetManager { if (info != null) { // Converting complex to dp. info.updateDimensions(mDisplayMetrics); + } else { + Log.e(TAG, "App widget provider info is null. PackageName=" + mPackageName + + " appWidgetId-" + appWidgetId); } return info; } catch (RemoteException e) { 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/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index 3e23762a4f70..b29b52d67310 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -127,4 +127,15 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "impulse_velocity_strategy_for_touch_navigation" + is_exported: true + namespace: "virtual_devices" + description: "Use impulse velocity strategy during conversion of touch navigation flings into Dpad events" + bug: "338426241" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ 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 af1301140358..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. * @@ -753,6 +756,9 @@ public final class AttributionSource implements Parcelable { @FlaggedApi(Flags.FLAG_SET_NEXT_ATTRIBUTION_SOURCE) public @NonNull Builder setNextAttributionSource(@NonNull AttributionSource value) { checkNotUsed(); + if (value == null) { + throw new IllegalArgumentException("Null AttributionSource not permitted."); + } mBuilderFieldsSet |= 0x20; mAttributionSourceState.next = new AttributionSourceState[]{value.mAttributionSourceState}; diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 9e316a2d3560..c8cae822570e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -4353,13 +4353,6 @@ public class Intent implements Parcelable, Cloneable { "android.intent.extra.BRIGHTNESS_DIALOG_IS_FULL_WIDTH"; /** - * Activity Action: Shows the contrast setting dialog. - * @hide - */ - public static final String ACTION_SHOW_CONTRAST_DIALOG = - "com.android.intent.action.SHOW_CONTRAST_DIALOG"; - - /** * Broadcast Action: A global button was pressed. Includes a single * extra field, {@link #EXTRA_KEY_EVENT}, containing the key event that * caused the broadcast. diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index b074e8b0c31b..2e252c12c51d 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -60,6 +60,10 @@ import android.util.AndroidException; * {@link android.app.PendingIntent#getIntentSender() PendingIntent.getIntentSender()}. */ public class IntentSender implements Parcelable { + private static final Bundle SEND_INTENT_DEFAULT_OPTIONS = + ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT).toBundle(); + @UnsupportedAppUsage private final IIntentSender mTarget; IBinder mWhitelistToken; @@ -161,7 +165,8 @@ public class IntentSender implements Parcelable { */ public void sendIntent(Context context, int code, Intent intent, OnFinished onFinished, Handler handler) throws SendIntentException { - sendIntent(context, code, intent, onFinished, handler, null, null /* options */); + sendIntent(context, code, intent, onFinished, handler, null, + SEND_INTENT_DEFAULT_OPTIONS); } /** @@ -194,7 +199,7 @@ public class IntentSender implements Parcelable { OnFinished onFinished, Handler handler, String requiredPermission) throws SendIntentException { sendIntent(context, code, intent, onFinished, handler, requiredPermission, - null /* options */); + SEND_INTENT_DEFAULT_OPTIONS); } /** diff --git a/core/java/android/content/PermissionChecker.java b/core/java/android/content/PermissionChecker.java index 0e3217d7071d..cb8eb835154b 100644 --- a/core/java/android/content/PermissionChecker.java +++ b/core/java/android/content/PermissionChecker.java @@ -73,13 +73,12 @@ public final class PermissionChecker { public static final int PERMISSION_GRANTED = PermissionCheckerManager.PERMISSION_GRANTED; /** - * The permission is denied. Applicable only to runtime and app op permissions. + * The permission is denied. Applicable only to runtime permissions. * * <p>Returned when: * <ul> * <li>the runtime permission is granted, but the corresponding app op is denied * for runtime permissions.</li> - * <li>the app ops is ignored for app op permissions.</li> * </ul> * * @hide diff --git a/core/java/android/content/TEST_MAPPING b/core/java/android/content/TEST_MAPPING index a2cfbf5aa5bd..41a4288eae5c 100644 --- a/core/java/android/content/TEST_MAPPING +++ b/core/java/android/content/TEST_MAPPING @@ -56,6 +56,10 @@ } ], "file_patterns": ["(/|^)Context.java", "(/|^)ContextWrapper.java"] + }, + { + "name": "CtsWindowManagerBackgroundActivityTestCases", + "file_patterns": ["(/|^)IntentSender.java"] } ], "ravenwood-presubmit": [ @@ -63,5 +67,7 @@ "name": "CtsContentTestCasesRavenwood", "host": true } + ], + "postsubmit": [ ] } diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java index 8220313a9197..57ee622de910 100644 --- a/core/java/android/content/pm/CrossProfileApps.java +++ b/core/java/android/content/pm/CrossProfileApps.java @@ -326,6 +326,7 @@ public class CrossProfileApps { * @return whether the specified user is a profile. */ @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE) + @SuppressWarnings("UserHandleName") public boolean isProfile(@NonNull UserHandle userHandle) { // Note that this is not a security check, but rather a check for correct use. // The actual security check is performed by UserManager. @@ -343,6 +344,7 @@ public class CrossProfileApps { * @return whether the specified user is a managed profile. */ @FlaggedApi(FLAG_ALLOW_QUERYING_PROFILE_TYPE) + @SuppressWarnings("UserHandleName") public boolean isManagedProfile(@NonNull UserHandle userHandle) { // Note that this is not a security check, but rather a check for correct use. // The actual security check is performed by UserManager. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 83285e0b8e8b..c506c9741635 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -270,11 +270,20 @@ public abstract class PackageManager { /** * Application level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for a app to inform the installer that a file containing the app's android - * safety label data is bundled into the APK at the given path. + * safety label data is bundled into the APK as a raw resource. + * + * <p>For example: + * <pre> + * <application> + * <property + * android:name="android.content.PROPERTY_ANDROID_SAFETY_LABEL" + * android:resource="@raw/app-metadata"/> + * </application> + * </pre> * @hide */ - public static final String PROPERTY_ANDROID_SAFETY_LABEL_PATH = - "android.content.SAFETY_LABEL_PATH"; + public static final String PROPERTY_ANDROID_SAFETY_LABEL = + "android.content.PROPERTY_ANDROID_SAFETY_LABEL"; /** * A property value set within the manifest. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 4b579e7db9f8..1f6730b9e3f9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2628,6 +2628,15 @@ public class PackageParser { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + targetCode)) { + Slog.w(TAG, "Package requires development platform " + targetCode + + ", returning current version " + Build.VERSION.SDK_INT); + return Build.VERSION.SDK_INT; + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + targetCode @@ -2699,6 +2708,15 @@ public class PackageParser { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + minCode)) { + Slog.w(TAG, "Package requires min development platform " + minCode + + ", returning current version " + Build.VERSION.SDK_INT); + return Build.VERSION.SDK_INT; + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + minCode 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 205f1e9c1f5c..061e7f711ba5 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -137,6 +137,18 @@ flag { } flag { + name: "get_package_storage_stats" + namespace: "system_performance" + is_exported: true + description: "Add dumpsys entry point for package StorageStats" + bug: "332905331" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "provide_info_of_apk_in_apex" is_exported: true namespace: "package_manager_service" @@ -242,9 +254,25 @@ 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." bug: "316916801" is_fixed_read_only: true } + +flag { + name: "package_restart_query_disabled_by_default" + namespace: "package_manager_service" + description: "Feature flag to register broadcast receiver only support package restart query." + bug: "300309050" + is_fixed_read_only: true +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index cd1913bb26d9..e2a131c0d527 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -240,3 +240,20 @@ flag { description: "Add entrypoint in Settings Reset options for deleting private space when lock is forgotten" bug: "329601751" } + +flag { + name: "allow_main_user_to_access_blocked_number_provider" + namespace: "multiuser" + description: "Allow MAIN user to access blocked number provider" + bug: "338579331" +} + +flag { + name: "restrict_quiet_mode_credential_bug_fix_to_managed_profiles" + namespace: "profile_experiences" + description: "Use user states to check the state of quiet mode for managed profiles only" + bug: "332812630" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java index 153dd9a93490..c7403c0ea98c 100644 --- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java @@ -316,6 +316,15 @@ public class FrameworkParsingPackageUtils { return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + minCode)) { + Slog.w(TAG, "Parsed package requires min development platform " + minCode + + ", returning current version " + Build.VERSION.SDK_INT); + return input.success(Build.VERSION.SDK_INT); + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, @@ -368,19 +377,27 @@ public class FrameworkParsingPackageUtils { return input.success(targetVers); } + // If it's a pre-release SDK and the codename matches this platform, it + // definitely targets this SDK. + if (matchTargetCode(platformSdkCodenames, targetCode)) { + return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + targetCode)) { + Slog.w(TAG, "Parsed package requires development platform " + targetCode + + ", returning current version " + Build.VERSION.SDK_INT); + return input.success(Build.VERSION.SDK_INT); + } + try { if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) { return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); } } catch (IllegalArgumentException e) { - // isAtMost() throws it when encountering an older SDK codename - return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, e.getMessage()); - } - - // If it's a pre-release SDK and the codename matches this platform, it - // definitely targets this SDK. - if (matchTargetCode(platformSdkCodenames, targetCode)) { - return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK"); } // Otherwise, we're looking at an incompatible pre-release SDK. diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 885f4c5e8ec4..982224b026bc 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -806,7 +806,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration * * <aside class="note"><b>Note:</b> If the app targets * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} - * or after, The width measurement reflects the window size without excluding insets. + * or after, the width measurement reflects the window size without excluding insets. * Otherwise, the measurement excludes window insets even when the app is displayed edge to edge * using {@link android.view.Window#setDecorFitsSystemWindows(boolean) * Window#setDecorFitsSystemWindows(boolean)}.</aside> 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/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index d683d72f17be..1eb466cb10a3 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -731,6 +731,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * commits, or is rolled back, either explicitly or by a call to * {@link #yieldIfContendedSafely}. */ + // TODO(340874899) Provide an Executor overload public void beginTransactionWithListener( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, true); @@ -760,6 +761,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * transaction begins, commits, or is rolled back, either * explicitly or by a call to {@link #yieldIfContendedSafely}. */ + // TODO(340874899) Provide an Executor overload public void beginTransactionWithListenerNonExclusive( @Nullable SQLiteTransactionListener transactionListener) { beginTransaction(transactionListener, false); @@ -785,6 +787,8 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </pre> */ + // TODO(340874899) Provide an Executor overload + @SuppressLint("ExecutorRegistration") @FlaggedApi(Flags.FLAG_SQLITE_APIS_35) public void beginTransactionWithListenerReadOnly( @Nullable SQLiteTransactionListener transactionListener) { 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 2191fd58d117..708f8a173aba 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -39,7 +39,6 @@ import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Point; import android.hardware.CameraExtensionSessionStats; -import android.hardware.CameraIdRemapping; import android.hardware.CameraStatus; import android.hardware.ICameraService; import android.hardware.ICameraServiceListener; @@ -69,6 +68,7 @@ import android.os.SystemProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.Size; import android.view.Display; @@ -379,7 +379,8 @@ public final class CameraManager { */ @NonNull public Set<Set<String>> getConcurrentCameraIds() throws CameraAccessException { - return CameraManagerGlobal.get().getConcurrentCameraIds(); + return CameraManagerGlobal.get().getConcurrentCameraIds(mContext.getDeviceId(), + getDevicePolicyFromContext(mContext)); } /** @@ -418,7 +419,8 @@ public final class CameraManager { @NonNull Map<String, SessionConfiguration> cameraIdAndSessionConfig) throws CameraAccessException { return CameraManagerGlobal.get().isConcurrentSessionConfigurationSupported( - cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion); + cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion, + mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); } /** @@ -794,7 +796,7 @@ public final class CameraManager { boolean hasConcurrentStreams = CameraManagerGlobal.get().cameraIdHasConcurrentStreamsLocked(cameraId, - mContext.getDeviceId()); + mContext.getDeviceId(), getDevicePolicyFromContext(mContext)); metadata.setHasMandatoryConcurrentStreams(hasConcurrentStreams); Size displaySize = getDisplaySize(); @@ -835,7 +837,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>(); @@ -971,8 +976,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; @@ -987,7 +990,7 @@ public final class CameraManager { callback, executor, characteristics, - physicalIdsToChars, + this, mContext.getApplicationInfo().targetSdkVersion, mContext, cameraDeviceSetup); ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks(); @@ -1992,17 +1995,6 @@ public final class CameraManager { } /** - * Remaps Camera Ids in the CameraService. - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) - public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping) - throws CameraAccessException, SecurityException, IllegalArgumentException { - CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping); - } - - /** * Injects session params into existing clients in the CameraService. * * @param cameraId The camera id of client to inject session params into. @@ -2097,7 +2089,7 @@ public final class CameraManager { // Opened Camera ID -> apk name map private final ArrayMap<DeviceCameraInfo, String> mOpenedDevices = new ArrayMap<>(); - private final Set<Set<String>> mConcurrentCameraIdCombinations = new ArraySet<>(); + private final Set<Set<DeviceCameraInfo>> mConcurrentCameraIdCombinations = new ArraySet<>(); // Registered availability callbacks and their executors private final ArrayMap<AvailabilityCallback, Executor> mCallbackMap = new ArrayMap<>(); @@ -2113,13 +2105,6 @@ public final class CameraManager { private final Object mLock = new Object(); - /** - * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state - * in the CameraService every time we connect to it, including when the CameraService - * Binder dies and we reconnect to it. - */ - @Nullable private CameraIdRemapping mActiveCameraIdRemapping; - // Access only through getCameraService to deal with binder death private ICameraService mCameraService; private boolean mHasOpenCloseListenerPermission = false; @@ -2256,7 +2241,13 @@ public final class CameraManager { ConcurrentCameraIdCombination[] cameraIdCombinations = cameraService.getConcurrentCameraIds(); for (ConcurrentCameraIdCombination comb : cameraIdCombinations) { - mConcurrentCameraIdCombinations.add(comb.getConcurrentCameraIdCombination()); + Set<Pair<String, Integer>> combination = + comb.getConcurrentCameraIdCombination(); + Set<DeviceCameraInfo> deviceCameraInfoSet = new ArraySet<>(); + for (Pair<String, Integer> entry : combination) { + deviceCameraInfoSet.add(new DeviceCameraInfo(entry.first, entry.second)); + } + mConcurrentCameraIdCombinations.add(deviceCameraInfoSet); } } catch (ServiceSpecificException e) { // Unexpected failure @@ -2265,41 +2256,6 @@ public final class CameraManager { } catch (RemoteException e) { // Camera service died in all probability } - - if (mActiveCameraIdRemapping != null) { - try { - cameraService.remapCameraIds(mActiveCameraIdRemapping); - } catch (ServiceSpecificException e) { - // Unexpected failure, ignore and continue. - Log.e(TAG, "Unable to remap camera Ids in the camera service"); - } catch (RemoteException e) { - // Camera service died in all probability - } - } - } - - /** Updates the cameraIdRemapping state in the CameraService. */ - public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping) - throws CameraAccessException, SecurityException { - synchronized (mLock) { - ICameraService cameraService = getCameraService(); - if (cameraService == null) { - throw new CameraAccessException( - CameraAccessException.CAMERA_DISCONNECTED, - "Camera service is currently unavailable."); - } - - try { - cameraService.remapCameraIds(cameraIdRemapping); - mActiveCameraIdRemapping = cameraIdRemapping; - } catch (ServiceSpecificException e) { - throw ExceptionUtils.throwAsPublicException(e); - } catch (RemoteException e) { - throw new CameraAccessException( - CameraAccessException.CAMERA_DISCONNECTED, - "Camera service is currently unavailable."); - } - } } /** Injects session params into an existing client for cameraid. */ @@ -2341,13 +2297,12 @@ public final class CameraManager { return cameraIds.toArray(new String[0]); } - private Set<Set<String>> extractConcurrentCameraIdListLocked() { + private Set<Set<String>> extractConcurrentCameraIdListLocked(int deviceId, + int devicePolicy) { Set<Set<String>> concurrentCameraIds = new ArraySet<>(); - for (Set<String> cameraIds : mConcurrentCameraIdCombinations) { + for (Set<DeviceCameraInfo> deviceCameraInfos : mConcurrentCameraIdCombinations) { Set<String> extractedCameraIds = new ArraySet<>(); - for (String cameraId : cameraIds) { - // TODO(b/291736219): This to be made device-aware. - DeviceCameraInfo info = new DeviceCameraInfo(cameraId, DEVICE_ID_DEFAULT); + for (DeviceCameraInfo info : deviceCameraInfos) { // if the camera id status is NOT_PRESENT or ENUMERATING; skip the device. // TODO: Would a device status NOT_PRESENT ever be in the map ? it gets removed // in the callback anyway. @@ -2360,9 +2315,14 @@ public final class CameraManager { || status == ICameraServiceListener.STATUS_NOT_PRESENT) { continue; } - extractedCameraIds.add(cameraId); + if (shouldHideCamera(deviceId, devicePolicy, info)) { + continue; + } + extractedCameraIds.add(info.mCameraId); + } + if (!extractedCameraIds.isEmpty()) { + concurrentCameraIds.add(extractedCameraIds); } - concurrentCameraIds.add(extractedCameraIds); } return concurrentCameraIds; } @@ -2523,12 +2483,13 @@ public final class CameraManager { return cameraIds; } - public @NonNull Set<Set<String>> getConcurrentCameraIds() { + public @NonNull Set<Set<String>> getConcurrentCameraIds(int deviceId, int devicePolicy) { Set<Set<String>> concurrentStreamingCameraIds; synchronized (mLock) { // Try to make sure we have an up-to-date list of concurrent camera devices. connectCameraServiceLocked(); - concurrentStreamingCameraIds = extractConcurrentCameraIdListLocked(); + concurrentStreamingCameraIds = extractConcurrentCameraIdListLocked(deviceId, + devicePolicy); } // TODO: Some sort of sorting ? return concurrentStreamingCameraIds; @@ -2536,13 +2497,12 @@ public final class CameraManager { public boolean isConcurrentSessionConfigurationSupported( @NonNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations, - int targetSdkVersion) throws CameraAccessException { + int targetSdkVersion, int deviceId, int devicePolicy) + throws CameraAccessException { if (cameraIdsAndSessionConfigurations == null) { throw new IllegalArgumentException("cameraIdsAndSessionConfigurations was null"); } - // TODO(b/291736219): Check if this API needs to be made device-aware. - int size = cameraIdsAndSessionConfigurations.size(); if (size == 0) { throw new IllegalArgumentException("camera id and session combination is empty"); @@ -2552,14 +2512,20 @@ public final class CameraManager { // Go through all the elements and check if the camera ids are valid at least / // belong to one of the combinations returned by getConcurrentCameraIds() boolean subsetFound = false; - for (Set<String> combination : mConcurrentCameraIdCombinations) { - if (combination.containsAll(cameraIdsAndSessionConfigurations.keySet())) { + for (Set<DeviceCameraInfo> combination : mConcurrentCameraIdCombinations) { + Set<DeviceCameraInfo> infos = new ArraySet<>(); + for (String cameraId : cameraIdsAndSessionConfigurations.keySet()) { + infos.add(new DeviceCameraInfo(cameraId, + devicePolicy == DEVICE_POLICY_DEFAULT + ? DEVICE_ID_DEFAULT : deviceId)); + } + if (combination.containsAll(infos)) { subsetFound = true; } } if (!subsetFound) { Log.v(TAG, "isConcurrentSessionConfigurationSupported called with a subset of" - + "camera ids not returned by getConcurrentCameraIds"); + + " camera ids not returned by getConcurrentCameraIds"); return false; } CameraIdAndSessionConfiguration [] cameraIdsAndConfigs = @@ -2573,7 +2539,7 @@ public final class CameraManager { } try { return mCameraService.isConcurrentSessionConfigurationSupported( - cameraIdsAndConfigs, targetSdkVersion); + cameraIdsAndConfigs, targetSdkVersion, deviceId, devicePolicy); } catch (ServiceSpecificException e) { throw ExceptionUtils.throwAsPublicException(e); } catch (RemoteException e) { @@ -2592,8 +2558,10 @@ public final class CameraManager { * @return Whether the camera device was found in the set of combinations returned by * getConcurrentCameraIds */ - public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId) { - DeviceCameraInfo info = new DeviceCameraInfo(cameraId, deviceId); + public boolean cameraIdHasConcurrentStreamsLocked(String cameraId, int deviceId, + int devicePolicy) { + DeviceCameraInfo info = new DeviceCameraInfo(cameraId, + devicePolicy == DEVICE_POLICY_DEFAULT ? DEVICE_ID_DEFAULT : deviceId); if (!mDeviceStatus.containsKey(info)) { // physical camera ids aren't advertised in concurrent camera id combinations. if (DEBUG) { @@ -2602,8 +2570,8 @@ public final class CameraManager { } return false; } - for (Set<String> comb : mConcurrentCameraIdCombinations) { - if (comb.contains(cameraId)) { + for (Set<DeviceCameraInfo> comb : mConcurrentCameraIdCombinations) { + if (comb.contains(info)) { return true; } } diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 7754e328bbf9..de26384211a4 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -2359,7 +2359,10 @@ public abstract class CameraMetadata<TKey> { * FPS.</p> * <p>If the session configuration is not supported, the AE mode reported in the * CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p> - * <p>The application can observe the CapturerResult field + * <p>When this AE mode is enabled, the CaptureResult field + * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} will be present and not null. Otherwise, the + * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} field will not be present in the CaptureResult.</p> + * <p>The application can observe the CaptureResult field * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or * 'INACTIVE'.</p> * <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 5765a73fb3c7..1460515c2438 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2819,6 +2819,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>When low light boost is enabled by setting the AE mode to * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light * boost when the light level threshold is exceeded.</p> + * <p>This field is present in the CaptureResult when the AE mode is set to + * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'. Otherwise, the field is not present.</p> * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can * indicate when it is not being applied by returning 'INACTIVE'.</p> * <p>This key will be absent from the CaptureResult if AE mode is not set to 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..7f3c49dbb580 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; } @@ -1556,8 +1571,7 @@ public class CameraDeviceImpl extends CameraDevice } // Allow RAW formats, even when not advertised. - if (inputFormat == ImageFormat.RAW_PRIVATE || inputFormat == ImageFormat.RAW10 - || inputFormat == ImageFormat.RAW12 || inputFormat == ImageFormat.RAW_SENSOR) { + if (isRawFormat(inputFormat)) { return true; } @@ -1598,7 +1612,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 && @@ -1627,6 +1641,11 @@ public class CameraDeviceImpl extends CameraDevice } } + // Allow RAW formats, even when not advertised. + if (Flags.multiResRawReprocessing() && isRawFormat(inputFormat)) { + return; + } + if (validFormat == false) { throw new IllegalArgumentException("multi-resolution input format " + inputFormat + " is not valid"); @@ -2569,6 +2588,11 @@ public class CameraDeviceImpl extends CameraDevice return mCharacteristics; } + private boolean isRawFormat(int format) { + return (format == ImageFormat.RAW_PRIVATE || format == ImageFormat.RAW10 + || format == ImageFormat.RAW12 || format == ImageFormat.RAW_SENSOR); + } + /** * Listener for binder death. * @@ -2621,7 +2645,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/ConcurrentCameraIdCombination.java b/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java index 8f4d6365f05e..1a8bf1d880c9 100644 --- a/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java +++ b/core/java/android/hardware/camera2/utils/ConcurrentCameraIdCombination.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package android.hardware.camera2.utils; import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; +import android.util.ArraySet; +import android.util.Pair; -import java.util.HashSet; import java.util.Set; /** @@ -30,21 +32,21 @@ import java.util.Set; */ public class ConcurrentCameraIdCombination implements Parcelable { - private Set<String> mConcurrentCameraIds = new HashSet<>(); + private final Set<Pair<String, Integer>> mConcurrentCameraIdDeviceIdPairs = new ArraySet<>(); public static final @NonNull Parcelable.Creator<ConcurrentCameraIdCombination> CREATOR = - new Parcelable.Creator<ConcurrentCameraIdCombination>() { - @Override - public ConcurrentCameraIdCombination createFromParcel(Parcel in) { - return new ConcurrentCameraIdCombination(in); - } + new Parcelable.Creator<>() { + @Override + public ConcurrentCameraIdCombination createFromParcel(Parcel in) { + return new ConcurrentCameraIdCombination(in); + } - @Override - public ConcurrentCameraIdCombination[] newArray(int size) { - return new ConcurrentCameraIdCombination[size]; - } - }; + @Override + public ConcurrentCameraIdCombination[] newArray(int size) { + return new ConcurrentCameraIdCombination[size]; + } + }; private ConcurrentCameraIdCombination(Parcel in) { readFromParcel(in); @@ -57,9 +59,10 @@ public class ConcurrentCameraIdCombination implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(mConcurrentCameraIds.size()); - for (String cameraId : mConcurrentCameraIds) { - dest.writeString(cameraId); + dest.writeInt(mConcurrentCameraIdDeviceIdPairs.size()); + for (Pair<String, Integer> cameraIdDeviceIdPair : mConcurrentCameraIdDeviceIdPairs) { + dest.writeString(cameraIdDeviceIdPair.first); + dest.writeInt(cameraIdDeviceIdPair.second); } } @@ -67,7 +70,7 @@ public class ConcurrentCameraIdCombination implements Parcelable { * helper for CREATOR */ public void readFromParcel(Parcel in) { - mConcurrentCameraIds.clear(); + mConcurrentCameraIdDeviceIdPairs.clear(); int cameraCombinationSize = in.readInt(); if (cameraCombinationSize < 0) { throw new RuntimeException("cameraCombinationSize " + cameraCombinationSize @@ -78,14 +81,15 @@ public class ConcurrentCameraIdCombination implements Parcelable { if (cameraId == null) { throw new RuntimeException("Failed to read camera id from Parcel"); } - mConcurrentCameraIds.add(cameraId); + int deviceId = in.readInt(); + mConcurrentCameraIdDeviceIdPairs.add(new Pair<>(cameraId, deviceId)); } } /** * Get this concurrent camera id combination. */ - public Set<String> getConcurrentCameraIdCombination() { - return mConcurrentCameraIds; + public Set<Pair<String, Integer>> getConcurrentCameraIdCombination() { + return mConcurrentCameraIdDeviceIdPairs; } } 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/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java index d340f3ff70bb..3f2ef84a4ef0 100644 --- a/core/java/android/hardware/face/FaceManager.java +++ b/core/java/android/hardware/face/FaceManager.java @@ -732,6 +732,10 @@ public class FaceManager implements BiometricAuthenticator { /** * Get statically configured sensor properties. + * @deprecated Generally unsafe to use, use + * {@link FaceManager#addAuthenticatorsRegisteredCallback} API instead. + * In most cases this method will work as expected, but during early boot up, it will be + * null/empty and there is no way for the caller to know when it's actual value is ready. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 25bfb2ab91e7..2ded615580fd 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -1189,6 +1189,10 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing /** * Get statically configured sensor properties. + * @deprecated Generally unsafe to use, use + * {@link FingerprintManager#addAuthenticatorsRegisteredCallback} API instead. + * In most cases this method will work as expected, but during early boot up, it will be + * null/empty and there is no way for the caller to know when it's actual value is ready. * @hide */ @RequiresPermission(USE_BIOMETRIC_INTERNAL) 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..40d4fb6df4bc 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -55,7 +55,6 @@ interface IInputManager { int[] getInputDeviceIds(); // Enable/disable input device. - boolean isInputDeviceEnabled(int deviceId); void enableInputDevice(int deviceId); void disableInputDevice(int deviceId); @@ -148,8 +147,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 +170,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..9eabc8d53bb3 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -333,19 +333,6 @@ public final class InputManager { } /** - * Returns true if an input device is enabled. Should return true for most - * situations. Some system apps may disable an input device, for - * example to prevent unwanted touch events. - * - * @param id The input device Id. - * - * @hide - */ - public boolean isInputDeviceEnabled(int id) { - return mGlobal.isInputDeviceEnabled(id); - } - - /** * Enables an InputDevice. * <p> * Requires {@link android.Manifest.permission#DISABLE_INPUT_DEVICE}. @@ -992,21 +979,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 +1081,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 +1096,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..7b471806cfc1 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -411,18 +411,6 @@ public final class InputManagerGlobal { } /** - * @see InputManager#isInputDeviceEnabled(int) - */ - public boolean isInputDeviceEnabled(int id) { - try { - return mIm.isInputDeviceEnabled(id); - } catch (RemoteException ex) { - Log.w(TAG, "Could not check enabled status of input device with id = " + id); - throw ex.rethrowFromSystemServer(); - } - } - - /** * @see InputManager#enableInputDevice(int) */ public void enableInputDevice(int id) { @@ -1411,28 +1399,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 +1433,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/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java index 18d0b09faa14..163f9fa83fe2 100644 --- a/core/java/android/hardware/lights/Light.java +++ b/core/java/android/hardware/lights/Light.java @@ -65,6 +65,12 @@ public final class Light implements Parcelable { public static final int LIGHT_TYPE_KEYBOARD_BACKLIGHT = 10003; /** + * Type for keyboard microphone mute light. + * @hide + */ + public static final int LIGHT_TYPE_KEYBOARD_MIC_MUTE = 10004; + + /** * Capability for lights that could adjust its LED brightness. If the capability is not present * the LED can only be turned either on or off. */ @@ -92,6 +98,7 @@ public final class Light implements Parcelable { LIGHT_TYPE_INPUT, LIGHT_TYPE_PLAYER_ID, LIGHT_TYPE_KEYBOARD_BACKLIGHT, + LIGHT_TYPE_KEYBOARD_MIC_MUTE, }) public @interface LightType {} diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java index 66b0a426f35d..3a271b44eef2 100644 --- a/core/java/android/hardware/usb/DeviceFilter.java +++ b/core/java/android/hardware/usb/DeviceFilter.java @@ -18,6 +18,7 @@ package android.hardware.usb; import android.annotation.NonNull; import android.annotation.Nullable; +import android.hardware.usb.flags.Flags; import android.service.usb.UsbDeviceFilterProto; import android.util.Slog; @@ -57,9 +58,12 @@ public class DeviceFilter { public final String mProductName; // USB device serial number string (or null for unspecified) public final String mSerialNumber; + // USB interface name (or null for unspecified). This will be used when matching devices using + // the available interfaces. + public final String mInterfaceName; public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol, - String manufacturer, String product, String serialnum) { + String manufacturer, String product, String serialnum, String interfaceName) { mVendorId = vid; mProductId = pid; mClass = clasz; @@ -68,6 +72,7 @@ public class DeviceFilter { mManufacturerName = manufacturer; mProductName = product; mSerialNumber = serialnum; + mInterfaceName = interfaceName; } public DeviceFilter(UsbDevice device) { @@ -79,6 +84,7 @@ public class DeviceFilter { mManufacturerName = device.getManufacturerName(); mProductName = device.getProductName(); mSerialNumber = device.getSerialNumber(); + mInterfaceName = null; } public DeviceFilter(@NonNull DeviceFilter filter) { @@ -90,6 +96,7 @@ public class DeviceFilter { mManufacturerName = filter.mManufacturerName; mProductName = filter.mProductName; mSerialNumber = filter.mSerialNumber; + mInterfaceName = filter.mInterfaceName; } public static DeviceFilter read(XmlPullParser parser) @@ -102,7 +109,7 @@ public class DeviceFilter { String manufacturerName = null; String productName = null; String serialNumber = null; - + String interfaceName = null; int count = parser.getAttributeCount(); for (int i = 0; i < count; i++) { String name = parser.getAttributeName(i); @@ -114,6 +121,8 @@ public class DeviceFilter { productName = value; } else if ("serial-number".equals(name)) { serialNumber = value; + } else if ("interface-name".equals(name)) { + interfaceName = value; } else { int intValue; int radix = 10; @@ -144,7 +153,7 @@ public class DeviceFilter { } return new DeviceFilter(vendorId, productId, deviceClass, deviceSubclass, deviceProtocol, - manufacturerName, productName, serialNumber); + manufacturerName, productName, serialNumber, interfaceName); } public void write(XmlSerializer serializer) throws IOException { @@ -173,13 +182,25 @@ public class DeviceFilter { if (mSerialNumber != null) { serializer.attribute(null, "serial-number", mSerialNumber); } + if (mInterfaceName != null) { + serializer.attribute(null, "interface-name", mInterfaceName); + } serializer.endTag(null, "usb-device"); } - private boolean matches(int clasz, int subclass, int protocol) { - return ((mClass == -1 || clasz == mClass) && - (mSubclass == -1 || subclass == mSubclass) && - (mProtocol == -1 || protocol == mProtocol)); + private boolean matches(int usbClass, int subclass, int protocol) { + return ((mClass == -1 || usbClass == mClass) + && (mSubclass == -1 || subclass == mSubclass) + && (mProtocol == -1 || protocol == mProtocol)); + } + + private boolean matches(int usbClass, int subclass, int protocol, String interfaceName) { + if (Flags.enableInterfaceNameDeviceFilter()) { + return matches(usbClass, subclass, protocol) + && (mInterfaceName == null || mInterfaceName.equals(interfaceName)); + } else { + return matches(usbClass, subclass, protocol); + } } public boolean matches(UsbDevice device) { @@ -204,7 +225,7 @@ public class DeviceFilter { for (int i = 0; i < count; i++) { UsbInterface intf = device.getInterface(i); if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(), - intf.getInterfaceProtocol())) return true; + intf.getInterfaceProtocol(), intf.getName())) return true; } return false; @@ -320,11 +341,12 @@ public class DeviceFilter { @Override public String toString() { - return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + - ",mClass=" + mClass + ",mSubclass=" + mSubclass + - ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + - ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + - "]"; + return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId + + ",mClass=" + mClass + ",mSubclass=" + mSubclass + + ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName + + ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber + + ",mInterfaceName=" + mInterfaceName + + "]"; } /** diff --git a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig index 94df16030cdb..40e5ffb141ab 100644 --- a/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig +++ b/core/java/android/hardware/usb/flags/usb_framework_flags.aconfig @@ -16,3 +16,11 @@ flag { description: "Feature flag for the api to check if a port supports mode change" bug: "323470419" } + +flag { + name: "enable_interface_name_device_filter" + is_exported: true + namespace: "usb" + description: "Feature flag to enable interface name as a parameter for device filter" + bug: "312828160" +} diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 594ec18d9996..334b2316b268 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -173,6 +173,12 @@ public class NetworkPolicyManager { public static final String FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY = "low_power_standby"; /** @hide */ public static final String FIREWALL_CHAIN_NAME_BACKGROUND = "background"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_ALLOW = "metered_allow"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_DENY_USER = "metered_deny_user"; + /** @hide */ + public static final String FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN = "metered_deny_admin"; private static final boolean ALLOW_PLATFORM_APP_POLICY = true; diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index 05a3e182135c..fedc97dcf989 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -1387,7 +1387,11 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * @param scheme name or {@code null} if this is a relative Uri */ public Builder scheme(String scheme) { - this.scheme = scheme; + if (scheme != null) { + this.scheme = scheme.replaceAll("://", ""); + } else { + this.scheme = null; + } return this; } diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig index fea2c253e743..d4d1ed22dd4e 100644 --- a/core/java/android/net/vcn/flags.aconfig +++ b/core/java/android/net/vcn/flags.aconfig @@ -38,6 +38,16 @@ flag{ } flag{ + name: "enforce_main_user" + namespace: "vcn" + description: "Enforce main user to make VCN HSUM compatible" + bug: "310310661" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag{ name: "handle_seq_num_leap" namespace: "vcn" description: "Do not report bad network when there is a suspected sequence number leap" @@ -45,4 +55,14 @@ flag{ metadata { purpose: PURPOSE_BUGFIX } +} + +flag{ + name: "allow_disable_ipsec_loss_detector" + namespace: "vcn" + description: "Allow disabling IPsec packet loss detector" + bug: "336638836" + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file 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/BugreportManager.java b/core/java/android/os/BugreportManager.java index 960e84d671e7..a818df5f0a8e 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -252,7 +252,8 @@ public final class BugreportManager { params.getMode(), params.getFlags(), dsListener, - isScreenshotRequested); + isScreenshotRequested, + /* skipUserConsent = */ false); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (FileNotFoundException e) { @@ -313,6 +314,7 @@ public final class BugreportManager { bugreportFd.getFileDescriptor(), bugreportFile, /* keepBugreportOnRetrieval = */ false, + /* skipUserConsent = */ false, dsListener); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); diff --git a/core/java/android/os/IHintManager.aidl b/core/java/android/os/IHintManager.aidl index e057a8536fab..360b2ac4f3ca 100644 --- a/core/java/android/os/IHintManager.aidl +++ b/core/java/android/os/IHintManager.aidl @@ -18,6 +18,7 @@ package android.os; import android.os.IHintSession; +import android.hardware.power.ChannelConfig; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; @@ -27,6 +28,9 @@ interface IHintManager { * Creates a {@link Session} for the given set of threads and associates to a binder token. * Returns a config if creation is not supported, and HMS had to use the * legacy creation method. + * + * Throws UnsupportedOperationException if ADPF is not supported, and IllegalStateException + * if creation is supported but fails. */ IHintSession createHintSessionWithConfig(in IBinder token, in int[] threadIds, in long durationNanos, in SessionTag tag, out @nullable SessionConfig config); @@ -38,4 +42,12 @@ interface IHintManager { void setHintSessionThreads(in IHintSession hintSession, in int[] tids); int[] getHintSessionThreadIds(in IHintSession hintSession); + + /** + * Returns FMQ channel information for the caller, which it associates to a binder token. + * + * Throws IllegalStateException if FMQ channel creation fails. + */ + ChannelConfig getSessionChannel(in IBinder token); + oneway void closeSessionChannel(); } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index c6a92033b0d4..80d356614932 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -368,17 +368,18 @@ public class UserManager { public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering"; /** - * Specifies if a user is disallowed from being granted admin privileges. + * Restricts a user's ability to possess or grant admin privileges. * - * <p>This restriction limits ability of other admin users to grant admin - * privileges to selected user. + * <p>When set to <code>true</code>, this prevents the user from: + * <ul> + * <li>Becoming an admin</li> + * <li>Giving other users admin privileges</li> + * </ul> * - * <p>This restriction has no effect in a mode that does not allow multiple admins. + * <p>This restriction is only effective in environments where multiple admins are allowed. * - * <p>The default value is <code>false</code>. + * <p>Key for user restrictions. Type: Boolean. Default: <code>false</code>. * - * <p>Key for user restrictions. - * <p>Type: Boolean * @see DevicePolicyManager#addUserRestriction(ComponentName, String) * @see DevicePolicyManager#clearUserRestriction(ComponentName, String) * @see #getUserRestrictions() @@ -1930,12 +1931,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 +1950,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/os/VintfObject.java b/core/java/android/os/VintfObject.java index 505655775239..bb89e0791053 100644 --- a/core/java/android/os/VintfObject.java +++ b/core/java/android/os/VintfObject.java @@ -17,8 +17,11 @@ package android.os; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; +import android.app.ActivityThread; +import java.io.IOException; import java.util.Map; /** @@ -113,5 +116,20 @@ public class VintfObject { @TestApi public static native Long getTargetFrameworkCompatibilityMatrixVersion(); + /** + * Executes a shell command using shell user identity, and return the standard output in string. + * + * @hide + */ + private static @Nullable String runShellCommand(@NonNull String command) throws IOException { + var activityThread = ActivityThread.currentActivityThread(); + var instrumentation = activityThread.getInstrumentation(); + var automation = instrumentation.getUiAutomation(); + var pfd = automation.executeShellCommand(command); + try (var is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { + return new String(is.readAllBytes()); + } + } + private VintfObject() {} } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index d45a17f7194e..91ad22f51345 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -120,6 +120,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * StorageManager is the interface to the systems storage service. The storage @@ -2512,6 +2514,9 @@ public class StorageManager { return userId * PER_USER_RANGE + projectId; } + private static final Pattern PATTERN_USER_ID = Pattern.compile( + "(?i)^/storage/emulated/([0-9]+)"); + /** * Let StorageManager know that the quota type for a file on external storage should * be updated. Android tracks quotas for various media types. Consequently, this should be @@ -2541,26 +2546,35 @@ public class StorageManager { @SystemApi public void updateExternalStorageFileQuotaType(@NonNull File path, @QuotaType int quotaType) throws IOException { + if (!path.exists()) return; + long projectId; final String filePath = path.getCanonicalPath(); - int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE; - // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are also - // returned by enabling FLAG_INCLUDE_SHARED_PROFILE. - if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) { - volFlags |= FLAG_INCLUDE_SHARED_PROFILE; - } - final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags); - final StorageVolume volume = getStorageVolume(availableVolumes, path); - if (volume == null) { - Log.w(TAG, "Failed to update quota type for " + filePath); - return; - } - if (!volume.isEmulated()) { - // We only support quota tracking on emulated filesystems - return; + + final int userId; + final Matcher matcher = PATTERN_USER_ID.matcher(filePath); + if (matcher.find()) { + userId = Integer.parseInt(matcher.group(1)); + } else { // fallback + int volFlags = FLAG_REAL_STATE | FLAG_INCLUDE_INVISIBLE; + // If caller has MANAGE_EXTERNAL_STORAGE permission, results from User Profile(s) are + // also returned by enabling FLAG_INCLUDE_SHARED_PROFILE. + if (mContext.checkSelfPermission(MANAGE_EXTERNAL_STORAGE) == PERMISSION_GRANTED) { + volFlags |= FLAG_INCLUDE_SHARED_PROFILE; + } + final StorageVolume[] availableVolumes = getVolumeList(mContext.getUserId(), volFlags); + final StorageVolume volume = getStorageVolume(availableVolumes, path); + if (volume == null) { + Log.w(TAG, "Failed to update quota type for " + filePath); + return; + } + if (!volume.isEmulated()) { + // We only support quota tracking on emulated filesystems + return; + } + userId = volume.getOwner().getIdentifier(); } - final int userId = volume.getOwner().getIdentifier(); if (userId < 0) { throw new IllegalStateException("Failed to update quota type for " + filePath); } diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS index d2f4b50eaa1b..857bacd34714 100644 --- a/core/java/android/permission/OWNERS +++ b/core/java/android/permission/OWNERS @@ -1,14 +1,13 @@ # Bug component: 137825 -augale@google.com evanseverson@google.com fayey@google.com jaysullivan@google.com joecastro@google.com -kvakil@google.com mrulhania@google.com ntmyren@google.com rmacgregor@google.com theianchen@google.com yutingfang@google.com zhanghai@google.com +kiranmr@google.com diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java index 55bb430d1523..7e51cb020196 100644 --- a/core/java/android/permission/PermissionManager.java +++ b/core/java/android/permission/PermissionManager.java @@ -112,7 +112,7 @@ public final class PermissionManager { public static final int PERMISSION_GRANTED = 0; /** - * The permission is denied. Applicable only to runtime and app op permissions. + * The permission is denied. Applicable only to runtime permissions. * <p> * The app isn't expecting the permission to be denied so that a "no-op" action should be taken, * such as returning an empty result. 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/CallLog.java b/core/java/android/provider/CallLog.java index 120846ca593b..708c1966be8b 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -2017,7 +2017,7 @@ public class CallLog { return false; } final UserInfo userInfo = userManager.getUserInfo(userId); - return userInfo != null && !userInfo.isManagedProfile(); + return userInfo != null && !userInfo.isProfile(); } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e6ddf3556490..3738c266641b 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); @@ -11181,6 +11173,35 @@ public final class Settings { "visual_query_accessibility_detection_enabled"; /** + * Timeout to be used for unbinding to the configured remote + * {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService} if there are no + * requests in the queue. A value of -1 represents to never unbind. + * + * @hide + */ + public static final String ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS = + "on_device_intelligence_unbind_timeout_ms"; + + + /** + * Timeout that represents maximum idle time before which a callback should be populated. + * + * @hide + */ + public static final String ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS = + "on_device_intelligence_idle_timeout_ms"; + + /** + * Timeout to be used for unbinding to the configured remote + * {@link android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService} if there + * are no requests in the queue. A value of -1 represents to never unbind. + * + * @hide + */ + public static final String ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS = + "on_device_inference_unbind_timeout_ms"; + + /** * Control whether Night display is currently activated. * @hide */ @@ -14917,6 +14938,17 @@ public final class Settings { public static final String DROPBOX_TAG_PREFIX = "dropbox:"; /** + * Lines of kernel logs to include with system crash/ANR/etc. reports, as a + * prefix of the dropbox tag of the report type. For example, + * "kernel_logs_for_system_server_anr" controls the lines of kernel logs + * captured with system server ANR reports. 0 to disable. + * + * @hide + */ + @Readable + public static final String ERROR_KERNEL_LOG_PREFIX = "kernel_logs_for_"; + + /** * Lines of logcat to include with system crash/ANR/etc. reports, as a * prefix of the dropbox tag of the report type. For example, * "logcat_for_system_server_anr" controls the lines of logcat captured @@ -17000,6 +17032,28 @@ public final class Settings { */ public static final String ENABLE_BACK_ANIMATION = "enable_back_animation"; + /** + * An allow list of packages for which the user has granted the permission to communicate + * across profiles. + * + * @hide + */ + @Readable + @FlaggedApi(android.app.admin.flags.Flags.FLAG_BACKUP_CONNECTED_APPS_SETTINGS) + public static final String CONNECTED_APPS_ALLOWED_PACKAGES = + "connected_apps_allowed_packages"; + + /** + * A block list of packages for which the user has denied the permission to communicate + * across profiles. + * + * @hide + */ + @Readable + @FlaggedApi(android.app.admin.flags.Flags.FLAG_BACKUP_CONNECTED_APPS_SETTINGS) + public static final String CONNECTED_APPS_DISALLOWED_PACKAGES = + "connected_apps_disallowed_packages"; + /** @hide */ public static String zenModeToString(int mode) { if (mode == ZEN_MODE_IMPORTANT_INTERRUPTIONS) return "ZEN_MODE_IMPORTANT_INTERRUPTIONS"; if (mode == ZEN_MODE_ALARMS) return "ZEN_MODE_ALARMS"; diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING index d5ac7a7de461..2eb285dda0a2 100644 --- a/core/java/android/provider/TEST_MAPPING +++ b/core/java/android/provider/TEST_MAPPING @@ -8,6 +8,9 @@ } ] }, + { + "name": "CtsMediaProviderTestCases" + }, { "name": "CalendarProviderTests" }, 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/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 5f6bdbf193b9..71066ac7ac39 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -18,7 +18,7 @@ package android.service.dreams; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.service.dreams.Flags.dreamHandlesConfirmKeys; -import static android.service.dreams.Flags.dreamTracksFocus; +import static android.service.dreams.Flags.dreamHandlesBeingObscured; import android.annotation.FlaggedApi; import android.annotation.IdRes; @@ -433,7 +433,8 @@ public class DreamService extends Service implements Window.Callback { mTrackingConfirmKey = event.getKeyCode(); } case KeyEvent.ACTION_UP -> { - if (mTrackingConfirmKey != event.getKeyCode()) { + if (mTrackingConfirmKey == null + || mTrackingConfirmKey != event.getKeyCode()) { return true; } @@ -571,15 +572,6 @@ public class DreamService extends Service implements Window.Callback { /** {@inheritDoc} */ @Override public void onWindowFocusChanged(boolean hasFocus) { - if (!dreamTracksFocus()) { - return; - } - - try { - mDreamManager.onDreamFocusChanged(hasFocus); - } catch (RemoteException ex) { - // system server died - } } /** {@inheritDoc} */ @@ -1737,7 +1729,7 @@ public class DreamService extends Service implements Window.Callback { @Override public void comeToFront() { - if (!dreamTracksFocus()) { + if (!dreamHandlesBeingObscured()) { return; } diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 85f0368a7b5c..cf98bfe05faf 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -48,5 +48,6 @@ interface IDreamManager { void setSystemDreamComponent(in ComponentName componentName); void registerDreamOverlayService(in ComponentName componentName); void startDreamActivity(in Intent intent); - void onDreamFocusChanged(in boolean hasFocus); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE)") + oneway void setDreamIsObscured(in boolean isObscured); } diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig index a42eaff68917..54d950c18af8 100644 --- a/core/java/android/service/dreams/flags.aconfig +++ b/core/java/android/service/dreams/flags.aconfig @@ -39,8 +39,11 @@ flag { } flag { - name: "dream_tracks_focus" + name: "dream_handles_being_obscured" namespace: "communal" - description: "This flag enables the ability for dreams to track whether or not they have focus" - bug: "331798001" + description: "This flag enables the ability for dreams to handle being obscured" + bug: "337302237" + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 76889dfc300a..88da8ebc3f95 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Notification; import android.app.NotificationChannel; @@ -37,9 +38,7 @@ import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.util.Log; - import com.android.internal.os.SomeArgs; - import java.lang.annotation.Retention; import java.util.List; @@ -116,6 +115,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS */ protected Handler mHandler; + @SuppressLint("OnNameExpected") @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); diff --git a/core/java/android/service/notification/NotificationStats.java b/core/java/android/service/notification/NotificationStats.java index 07367df7bc91..caa0a9c78d47 100644 --- a/core/java/android/service/notification/NotificationStats.java +++ b/core/java/android/service/notification/NotificationStats.java @@ -19,6 +19,7 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Flags; import android.app.RemoteInput; @@ -229,6 +230,7 @@ public final class NotificationStats implements Parcelable { /** * Records that the user has replied to a notification that has a smart reply at least once. */ + @SuppressLint("GetterSetterNames") @FlaggedApi(Flags.FLAG_LIFETIME_EXTENSION_REFACTOR) public void setSmartReplied() { mSmartReplied = true; diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index 1d7091c52d7a..910c4626ea96 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -1007,6 +1007,7 @@ public final class ZenPolicy implements Parcelable { /** * Set whether priority channels are permitted to break through DND. */ + @SuppressLint("BuilderSetStyle") @FlaggedApi(Flags.FLAG_MODES_API) public @NonNull Builder allowPriorityChannels(boolean allow) { mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE; diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java index 793e58ac5d3b..293015f86cee 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java @@ -18,6 +18,9 @@ package android.service.ondeviceintelligence; import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + +import android.annotation.CallSuper; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.NonNull; @@ -40,13 +43,16 @@ import android.content.Intent; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.Handler; import android.os.IBinder; import android.os.ICancellationSignal; +import android.os.Looper; import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; +import android.util.Log; import android.util.Slog; import com.android.internal.infra.AndroidFuture; @@ -88,6 +94,14 @@ public abstract class OnDeviceIntelligenceService extends Service { private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName(); private volatile IRemoteProcessingService mRemoteProcessingService; + private Handler mHandler; + + @CallSuper + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */); + } /** * The {@link Intent} that must be declared as handled by the service. To be supported, the @@ -107,38 +121,49 @@ public abstract class OnDeviceIntelligenceService extends Service { @Override public final IBinder onBind(@NonNull Intent intent) { if (SERVICE_INTERFACE.equals(intent.getAction())) { - // TODO(326052028) : Move the remote method calls to an app handler from the binder - // thread. return new IOnDeviceIntelligenceService.Stub() { /** {@inheritDoc} */ @Override public void ready() { - OnDeviceIntelligenceService.this.onReady(); + mHandler.executeOrSendMessage( + obtainMessage(OnDeviceIntelligenceService::onReady, + OnDeviceIntelligenceService.this)); } @Override public void getVersion(RemoteCallback remoteCallback) { Objects.requireNonNull(remoteCallback); - OnDeviceIntelligenceService.this.onGetVersion(l -> { - Bundle b = new Bundle(); - b.putLong(OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, l); - remoteCallback.sendResult(b); - }); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onGetVersion, + OnDeviceIntelligenceService.this, l -> { + Bundle b = new Bundle(); + b.putLong( + OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, + l); + remoteCallback.sendResult(b); + })); } @Override public void listFeatures(int callerUid, IListFeaturesCallback listFeaturesCallback) { Objects.requireNonNull(listFeaturesCallback); - OnDeviceIntelligenceService.this.onListFeatures(callerUid, - wrapListFeaturesCallback(listFeaturesCallback)); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onListFeatures, + OnDeviceIntelligenceService.this, callerUid, + wrapListFeaturesCallback(listFeaturesCallback))); } @Override public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) { Objects.requireNonNull(featureCallback); - OnDeviceIntelligenceService.this.onGetFeature(callerUid, - id, wrapFeatureCallback(featureCallback)); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onGetFeature, + OnDeviceIntelligenceService.this, callerUid, + id, wrapFeatureCallback(featureCallback))); } @@ -147,9 +172,11 @@ public abstract class OnDeviceIntelligenceService extends Service { IFeatureDetailsCallback featureDetailsCallback) { Objects.requireNonNull(feature); Objects.requireNonNull(featureDetailsCallback); - - OnDeviceIntelligenceService.this.onGetFeatureDetails(callerUid, - feature, wrapFeatureDetailsCallback(featureDetailsCallback)); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onGetFeatureDetails, + OnDeviceIntelligenceService.this, callerUid, + feature, wrapFeatureDetailsCallback(featureDetailsCallback))); } @Override @@ -163,10 +190,13 @@ public abstract class OnDeviceIntelligenceService extends Service { transport = CancellationSignal.createTransport(); cancellationSignalFuture.complete(transport); } - OnDeviceIntelligenceService.this.onDownloadFeature(callerUid, - feature, - CancellationSignal.fromTransport(transport), - wrapDownloadCallback(downloadCallback)); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onDownloadFeature, + OnDeviceIntelligenceService.this, callerUid, + feature, + CancellationSignal.fromTransport(transport), + wrapDownloadCallback(downloadCallback))); } @Override @@ -174,9 +204,11 @@ public abstract class OnDeviceIntelligenceService extends Service { AndroidFuture<ParcelFileDescriptor> future) { Objects.requireNonNull(fileName); Objects.requireNonNull(future); - - OnDeviceIntelligenceService.this.onGetReadOnlyFileDescriptor(fileName, - future); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onGetReadOnlyFileDescriptor, + OnDeviceIntelligenceService.this, fileName, + future)); } @Override @@ -184,13 +216,15 @@ public abstract class OnDeviceIntelligenceService extends Service { Feature feature, RemoteCallback remoteCallback) { Objects.requireNonNull(feature); Objects.requireNonNull(remoteCallback); - - OnDeviceIntelligenceService.this.onGetReadOnlyFeatureFileDescriptorMap( - feature, parcelFileDescriptorMap -> { - Bundle bundle = new Bundle(); - parcelFileDescriptorMap.forEach(bundle::putParcelable); - remoteCallback.sendResult(bundle); - }); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onGetReadOnlyFeatureFileDescriptorMap, + OnDeviceIntelligenceService.this, feature, + parcelFileDescriptorMap -> { + Bundle bundle = new Bundle(); + parcelFileDescriptorMap.forEach(bundle::putParcelable); + remoteCallback.sendResult(bundle); + })); } @Override @@ -201,12 +235,18 @@ public abstract class OnDeviceIntelligenceService extends Service { @Override public void notifyInferenceServiceConnected() { - OnDeviceIntelligenceService.this.onInferenceServiceConnected(); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onInferenceServiceConnected, + OnDeviceIntelligenceService.this)); } @Override public void notifyInferenceServiceDisconnected() { - OnDeviceIntelligenceService.this.onInferenceServiceDisconnected(); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceIntelligenceService::onInferenceServiceDisconnected, + OnDeviceIntelligenceService.this)); } }; } @@ -222,7 +262,8 @@ public abstract class OnDeviceIntelligenceService extends Service { * @hide */ @TestApi - public void onReady() {} + public void onReady() { + } /** @@ -410,12 +451,16 @@ public abstract class OnDeviceIntelligenceService extends Service { Slog.v(TAG, "onGetReadOnlyFileDescriptor: " + fileName + " under internal app storage."); File f = new File(getBaseContext().getFilesDir(), fileName); + if (!f.exists()) { + f = new File(fileName); + } ParcelFileDescriptor pfd = null; try { pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor."); } catch (FileNotFoundException e) { Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned."); + future.completeExceptionally(e); } finally { future.complete(pfd); if (pfd != null) { diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java index 29a6db6a12a0..d00485cb1ca5 100644 --- a/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java +++ b/core/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java @@ -19,7 +19,10 @@ package android.service.ondeviceintelligence; import static android.app.ondeviceintelligence.OnDeviceIntelligenceManager.AUGMENT_REQUEST_CONTENT_BUNDLE_KEY; import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE; +import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; + import android.annotation.CallbackExecutor; +import android.annotation.CallSuper; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -48,6 +51,7 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.IBinder; import android.os.ICancellationSignal; +import android.os.Looper; import android.os.OutcomeReceiver; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; @@ -105,7 +109,35 @@ 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"; + + /** + * @hide + */ + public static final String DEVICE_CONFIG_UPDATE_BUNDLE_KEY = "device_config_update"; + private IRemoteStorageService mRemoteStorageService; + private Handler mHandler; + + @CallSuper + @Override + public void onCreate() { + super.onCreate(); + mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */); + } /** * @hide @@ -132,11 +164,15 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { transport = CancellationSignal.createTransport(); cancellationSignalFuture.complete(transport); } - OnDeviceSandboxedInferenceService.this.onTokenInfoRequest(callerUid, - feature, - request, - CancellationSignal.fromTransport(transport), - wrapTokenInfoCallback(tokenInfoCallback)); + + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceSandboxedInferenceService::onTokenInfoRequest, + OnDeviceSandboxedInferenceService.this, + callerUid, feature, + request, + CancellationSignal.fromTransport(transport), + wrapTokenInfoCallback(tokenInfoCallback))); } @Override @@ -158,13 +194,18 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { processingSignalTransport = ProcessingSignal.createTransport(); processingSignalFuture.complete(processingSignalTransport); } - OnDeviceSandboxedInferenceService.this.onProcessRequestStreaming(callerUid, - feature, - request, - requestType, - CancellationSignal.fromTransport(transport), - ProcessingSignal.fromTransport(processingSignalTransport), - wrapStreamingResponseCallback(callback)); + + + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceSandboxedInferenceService::onProcessRequestStreaming, + OnDeviceSandboxedInferenceService.this, callerUid, + feature, + request, + requestType, + CancellationSignal.fromTransport(transport), + ProcessingSignal.fromTransport(processingSignalTransport), + wrapStreamingResponseCallback(callback))); } @Override @@ -185,11 +226,14 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { processingSignalTransport = ProcessingSignal.createTransport(); processingSignalFuture.complete(processingSignalTransport); } - OnDeviceSandboxedInferenceService.this.onProcessRequest(callerUid, feature, - request, requestType, - CancellationSignal.fromTransport(transport), - ProcessingSignal.fromTransport(processingSignalTransport), - wrapResponseCallback(callback)); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceSandboxedInferenceService::onProcessRequest, + OnDeviceSandboxedInferenceService.this, callerUid, feature, + request, requestType, + CancellationSignal.fromTransport(transport), + ProcessingSignal.fromTransport(processingSignalTransport), + wrapResponseCallback(callback))); } @Override @@ -197,10 +241,11 @@ public abstract class OnDeviceSandboxedInferenceService extends Service { IProcessingUpdateStatusCallback callback) { Objects.requireNonNull(processingState); Objects.requireNonNull(callback); - - OnDeviceSandboxedInferenceService.this.onUpdateProcessingState(processingState, - wrapOutcomeReceiver(callback) - ); + mHandler.executeOrSendMessage( + obtainMessage( + OnDeviceSandboxedInferenceService::onUpdateProcessingState, + OnDeviceSandboxedInferenceService.this, processingState, + wrapOutcomeReceiver(callback))); } }; } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index d174bef90f9c..95897855586d 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1025,7 +1025,8 @@ public abstract class WallpaperService extends Service { mWallpaperDimAmount = (!mShouldDimByDefault) ? mCustomDimAmount : Math.max(mDefaultDimAmount, mCustomDimAmount); - if (!ENABLE_WALLPAPER_DIMMING || mBbqSurfaceControl == null + if (!ENABLE_WALLPAPER_DIMMING + || mBbqSurfaceControl == null || !mBbqSurfaceControl.isValid() || mWallpaperDimAmount == mPreviousWallpaperDimAmount) { return; } diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index cce4f7bf7b1f..a78a417300ab 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -310,6 +310,7 @@ public class DynamicLayout extends Layout { * @see Layout#getUseBoundsForWidth() * @see Layout.Builder#setUseBoundsForWidth(boolean) */ + @SuppressLint("MissingGetterMatchingBuilder") // The base class `Layout` has a getter. @NonNull @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public Builder setUseBoundsForWidth(boolean useBoundsForWidth) { diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 3dd3a9ea8baf..95460a3575eb 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -447,6 +447,7 @@ public class StaticLayout extends Layout { * @see Layout#getUseBoundsForWidth() * @see Layout.Builder#setUseBoundsForWidth(boolean) */ + @SuppressLint("MissingGetterMatchingBuilder") // The base class `Layout` has a getter. @NonNull @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public Builder setUseBoundsForWidth(boolean useBoundsForWidth) { diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig index 56df32847d74..70dc300e59d0 100644 --- a/core/java/android/text/flags/flags.aconfig +++ b/core/java/android/text/flags/flags.aconfig @@ -181,3 +181,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "missing_getter_apis" + namespace: "text" + description: "Fix the lint warning about missing getters." + bug: "340875345" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/tracing/flags.aconfig b/core/java/android/tracing/flags.aconfig index 74428aa10586..d7389bab8d6d 100644 --- a/core/java/android/tracing/flags.aconfig +++ b/core/java/android/tracing/flags.aconfig @@ -30,3 +30,11 @@ flag { is_fixed_read_only: true bug: "276433199" } + +flag { + name: "perfetto_view_capture_tracing" + namespace: "windowing_tools" + description: "Migrate ViewCapture tracing to Perfetto" + is_fixed_read_only: true + bug: "323166383" +} diff --git a/core/java/android/tracing/inputmethod/InputMethodDataSource.java b/core/java/android/tracing/inputmethod/InputMethodDataSource.java new file mode 100644 index 000000000000..5c5ad6947e4d --- /dev/null +++ b/core/java/android/tracing/inputmethod/InputMethodDataSource.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.tracing.inputmethod; + +import android.annotation.NonNull; +import android.tracing.perfetto.DataSource; +import android.tracing.perfetto.DataSourceInstance; +import android.tracing.perfetto.StartCallbackArguments; +import android.tracing.perfetto.StopCallbackArguments; +import android.util.proto.ProtoInputStream; + +/** + * @hide + */ +public final class InputMethodDataSource + extends DataSource<DataSourceInstance, Void, Void> { + public static final String DATA_SOURCE_NAME = "android.inputmethod"; + + @NonNull + private final Runnable mOnStartCallback; + @NonNull + private final Runnable mOnStopCallback; + + public InputMethodDataSource(@NonNull Runnable onStart, @NonNull Runnable onStop) { + super(DATA_SOURCE_NAME); + mOnStartCallback = onStart; + mOnStopCallback = onStop; + } + + @Override + public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) { + return new DataSourceInstance(this, instanceIndex) { + @Override + protected void onStart(StartCallbackArguments args) { + mOnStartCallback.run(); + } + + @Override + protected void onStop(StopCallbackArguments args) { + mOnStopCallback.run(); + } + }; + } +} diff --git a/core/java/android/tracing/perfetto/DataSource.java b/core/java/android/tracing/perfetto/DataSource.java index b9ab82cb63a9..4de7b62521d1 100644 --- a/core/java/android/tracing/perfetto/DataSource.java +++ b/core/java/android/tracing/perfetto/DataSource.java @@ -85,7 +85,7 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan new TracingContext<>(this, instanceIndex); fun.trace(ctx); - ctx.flush(); + nativeWritePackets(mNativeObj, ctx.getAndClearAllPendingTracePackets()); } while (nativePerfettoDsTraceIterateNext(mNativeObj)); } finally { nativePerfettoDsTraceIterateBreak(mNativeObj); @@ -130,7 +130,8 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan * @param params Params to initialize the datasource with. */ public void register(DataSourceParams params) { - nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy); + nativeRegisterDataSource(this.mNativeObj, params.bufferExhaustedPolicy, + params.willNotifyOnStop, params.noFlush); } /** @@ -163,8 +164,8 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan return this.createInstance(inputStream, instanceIndex); } - private static native void nativeRegisterDataSource( - long dataSourcePtr, int bufferExhaustedPolicy); + private static native void nativeRegisterDataSource(long dataSourcePtr, + int bufferExhaustedPolicy, boolean willNotifyOnStop, boolean noFlush); private static native long nativeCreate(DataSource thiz, String name); private static native void nativeFlushAll(long nativeDataSourcePointer); @@ -179,4 +180,6 @@ public abstract class DataSource<DataSourceInstanceType extends DataSourceInstan private static native boolean nativePerfettoDsTraceIterateNext(long dataSourcePtr); private static native void nativePerfettoDsTraceIterateBreak(long dataSourcePtr); private static native int nativeGetPerfettoDsInstanceIndex(long dataSourcePtr); + + private static native void nativeWritePackets(long dataSourcePtr, byte[][] packetData); } diff --git a/core/java/android/tracing/perfetto/DataSourceParams.java b/core/java/android/tracing/perfetto/DataSourceParams.java index 6cd04e3d9a8b..e50f9d722fad 100644 --- a/core/java/android/tracing/perfetto/DataSourceParams.java +++ b/core/java/android/tracing/perfetto/DataSourceParams.java @@ -46,12 +46,67 @@ public class DataSourceParams { // after a while. public static final int PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT = 1; - public static DataSourceParams DEFAULTS = - new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP); + public static DataSourceParams DEFAULTS = new DataSourceParams.Builder().build(); - public DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy) { + private DataSourceParams(@PerfettoDsBufferExhausted int bufferExhaustedPolicy, + boolean willNotifyOnStop, boolean noFlush) { this.bufferExhaustedPolicy = bufferExhaustedPolicy; + this.willNotifyOnStop = willNotifyOnStop; + this.noFlush = noFlush; } public final @PerfettoDsBufferExhausted int bufferExhaustedPolicy; + public final boolean willNotifyOnStop; + public final boolean noFlush; + + /** + * DataSource Parameters builder + * + * @hide + */ + public static final class Builder { + /** + * Specify behavior when running out of shared memory buffer space. + */ + public Builder setBufferExhaustedPolicy(@PerfettoDsBufferExhausted int value) { + this.mBufferExhaustedPolicy = value; + return this; + } + + /** + * If true, the data source is expected to ack the stop request through the + * NotifyDataSourceStopped() IPC. If false, the service won't wait for an ack. + * Set this parameter to false when dealing with potentially frozen producers + * that wouldn't be able to quickly ack the stop request. + * + * Default value: true + */ + public Builder setWillNotifyOnStop(boolean value) { + this.mWillNotifyOnStop = value; + return this; + } + + /** + * If true, the service won't emit flush requests for this data source. This + * allows the service to reduce the flush-related IPC traffic and better deal + * with frozen producers (see go/perfetto-frozen). + */ + public Builder setNoFlush(boolean value) { + this.mNoFlush = value; + return this; + } + + /** + * Build the DataSource parameters. + */ + public DataSourceParams build() { + return new DataSourceParams( + this.mBufferExhaustedPolicy, this.mWillNotifyOnStop, this.mNoFlush); + } + + private @PerfettoDsBufferExhausted int mBufferExhaustedPolicy = + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP; + private boolean mWillNotifyOnStop = true; + private boolean mNoFlush = false; + } } diff --git a/core/java/android/tracing/perfetto/TracingContext.java b/core/java/android/tracing/perfetto/TracingContext.java index 6b7df5441427..98cb4c838eed 100644 --- a/core/java/android/tracing/perfetto/TracingContext.java +++ b/core/java/android/tracing/perfetto/TracingContext.java @@ -59,19 +59,6 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance, T } /** - * Forces a commit of the thread-local tracing data written so far to the - * service. This is almost never required (tracing data is periodically - * committed as trace pages are filled up) and has a non-negligible - * performance hit (requires an IPC + refresh of the current thread-local - * chunk). The only case when this should be used is when handling OnStop() - * asynchronously, to ensure sure that the data is committed before the - * Stop timeout expires. - */ - public void flush() { - nativeFlush(mDataSource.mNativeObj, getAndClearAllPendingTracePackets()); - } - - /** * Can optionally be used to store custom per-sequence * session data, which is not reset when incremental state is cleared * (e.g. configuration options). @@ -109,7 +96,7 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance, T return incrementalState; } - private byte[][] getAndClearAllPendingTracePackets() { + protected byte[][] getAndClearAllPendingTracePackets() { byte[][] res = new byte[mTracePackets.size()][]; for (int i = 0; i < mTracePackets.size(); i++) { ProtoOutputStream tracePacket = mTracePackets.get(i); @@ -120,8 +107,6 @@ public class TracingContext<DataSourceInstanceType extends DataSourceInstance, T return res; } - private static native void nativeFlush(long dataSourcePtr, byte[][] packetData); - private static native Object nativeGetCustomTls(long nativeDsPtr); private static native void nativeSetCustomTls(long nativeDsPtr, Object tlsState); diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 4475418e1e57..6464239eb2fc 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -916,6 +916,12 @@ public final class Display { * {@code getWindowManager()} or {@code getSystemService(Context.WINDOW_SERVICE)}), the * size of the current app window is returned. As a result, in multi-window mode, the * returned size can be smaller than the size of the device screen. + * The returned window size can vary depending on API level: + * <ul> + * <li>API level 35 and above, the window size will be returned. + * <li>API level 34 and below, the window size minus system decoration areas and + * display cutout is returned. + * </ul> * <li>If size is requested from a non-activity context (for example, the application * context, where the WindowManager is accessed by * {@code getApplicationContext().getSystemService(Context.WINDOW_SERVICE)}), the @@ -924,9 +930,10 @@ public final class Display { * <li>API level 29 and below — The size of the entire display (based on * current rotation) minus system decoration areas is returned. * <li>API level 30 and above — The size of the top running activity in the - * current process is returned. If the current process has no running - * activities, the size of the device default display, including system - * decoration areas, is returned. + * current process is returned, system decoration areas exclusion follows the + * behavior defined above, based on the caller's API level. If the current + * process has no running activities, the size of the device default display, + * including system decoration areas, is returned. * </ul> * </ul> * 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/FocusFinder.java b/core/java/android/view/FocusFinder.java index 35b137a322e3..c5b6aa7118cb 100644 --- a/core/java/android/view/FocusFinder.java +++ b/core/java/android/view/FocusFinder.java @@ -129,7 +129,7 @@ public class FocusFinder { } ViewGroup effective = null; ViewParent nextParent = focused.getParent(); - do { + while (nextParent instanceof ViewGroup) { if (nextParent == root) { return effective != null ? effective : root; } @@ -143,7 +143,7 @@ public class FocusFinder { effective = vg; } nextParent = nextParent.getParent(); - } while (nextParent instanceof ViewGroup); + } return root; } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index 9ff29a81a5c6..4837ee5a797c 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -26,11 +26,13 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UiContext; +import android.app.Activity; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.os.Message; import android.os.StrictMode; import android.os.SystemClock; @@ -299,6 +301,11 @@ public class GestureDetector { private VelocityTracker mVelocityTracker; /** + * Determines strategy for velocity calculation + */ + private @VelocityTracker.VelocityTrackerStrategy int mVelocityTrackerStrategy; + + /** * Consistency verifier for debugging purposes. */ private final InputEventConsistencyVerifier mInputEventConsistencyVerifier = @@ -347,17 +354,17 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener. - * This variant of the constructor should be used from a non-UI thread + * This variant of the constructor should be used from a non-UI thread * (as it allows specifying the Handler). - * + * * @param listener the listener invoked for all the callbacks, this must * not be null. * @param handler the handler to use * * @throws NullPointerException if {@code listener} is null. * - * @deprecated Use {@link #GestureDetector(android.content.Context, - * android.view.GestureDetector.OnGestureListener, android.os.Handler)} instead. + * @deprecated Use {@link #GestureDetector(Context, GestureDetector.OnGestureListener, Handler)} + * instead. */ @Deprecated public GestureDetector(@NonNull OnGestureListener listener, @Nullable Handler handler) { @@ -367,15 +374,14 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener. * You may only use this constructor from a UI thread (this is the usual situation). - * @see android.os.Handler#Handler() - * + * @see Handler#Handler() + * * @param listener the listener invoked for all the callbacks, this must * not be null. - * + * * @throws NullPointerException if {@code listener} is null. * - * @deprecated Use {@link #GestureDetector(android.content.Context, - * android.view.GestureDetector.OnGestureListener)} instead. + * @deprecated Use {@link #GestureDetector(Context, GestureDetector.OnGestureListener)} instead. */ @Deprecated public GestureDetector(@NonNull OnGestureListener listener) { @@ -384,10 +390,10 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener. - * You may only use this constructor from a {@link android.os.Looper} thread. - * @see android.os.Handler#Handler() + * You may only use this constructor from a {@link Looper} thread. + * @see Handler#Handler() * - * @param context An {@link android.app.Activity} or a {@link Context} created from + * @param context An {@link Activity} or a {@link Context} created from * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or @@ -404,10 +410,10 @@ public class GestureDetector { /** * Creates a GestureDetector with the supplied listener that runs deferred events on the - * thread associated with the supplied {@link android.os.Handler}. - * @see android.os.Handler#Handler() + * thread associated with the supplied {@link Handler}. + * @see Handler#Handler() * - * @param context An {@link android.app.Activity} or a {@link Context} created from + * @param context An {@link Activity} or a {@link Context} created from * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or @@ -419,6 +425,31 @@ public class GestureDetector { */ public GestureDetector(@Nullable @UiContext Context context, @NonNull OnGestureListener listener, @Nullable Handler handler) { + this(context, listener, handler, VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT); + } + + /** + * Creates a GestureDetector with the supplied listener that runs deferred events on the + * thread associated with the supplied {@link Handler}. + * @see Handler#Handler() + * + * @param context An {@link Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} + * @param listener the listener invoked for all the callbacks, this must + * not be null. If the listener implements the {@link OnDoubleTapListener} or + * {@link OnContextClickListener} then it will also be set as the listener for + * these callbacks (for example when using the {@link SimpleOnGestureListener}). + * @param handler the handler to use for running deferred listener events. + * @param velocityTrackerStrategy strategy to use for velocity calculation of scroll/fling + * events. + * + * @throws NullPointerException if {@code listener} is null. + * + * @hide + */ + public GestureDetector(@Nullable @UiContext Context context, + @NonNull OnGestureListener listener, @Nullable Handler handler, + @VelocityTracker.VelocityTrackerStrategy int velocityTrackerStrategy) { if (handler != null) { mHandler = new GestureHandler(handler); } else { @@ -431,15 +462,16 @@ public class GestureDetector { if (listener instanceof OnContextClickListener) { setContextClickListener((OnContextClickListener) listener); } + mVelocityTrackerStrategy = velocityTrackerStrategy; init(context); } - + /** * Creates a GestureDetector with the supplied listener that runs deferred events on the - * thread associated with the supplied {@link android.os.Handler}. - * @see android.os.Handler#Handler() + * thread associated with the supplied {@link Handler}. + * @see Handler#Handler() * - * @param context An {@link android.app.Activity} or a {@link Context} created from + * @param context An {@link Activity} or a {@link Context} created from * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. @@ -547,7 +579,7 @@ public class GestureDetector { mCurrentMotionEvent = MotionEvent.obtain(ev); if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker = VelocityTracker.obtain(mVelocityTrackerStrategy); } mVelocityTracker.addMovement(ev); 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/ImeBackAnimationController.java b/core/java/android/view/ImeBackAnimationController.java index 30daaf7f6242..fc1852d739e2 100644 --- a/core/java/android/view/ImeBackAnimationController.java +++ b/core/java/android/view/ImeBackAnimationController.java @@ -38,6 +38,8 @@ import android.window.OnBackAnimationCallback; import com.android.internal.inputmethod.SoftInputShowHideReason; +import java.io.PrintWriter; + /** * Controller for IME predictive back animation * @@ -134,7 +136,9 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { @Override public void onBackInvoked() { - if (!isBackAnimationAllowed()) { + if (!isBackAnimationAllowed() || !mIsPreCommitAnimationInProgress) { + // play regular hide animation if back-animation is not allowed or if insets control has + // been cancelled by the system (this can happen in split screen for example) mInsetsController.hide(ime()); return; } @@ -267,4 +271,24 @@ public class ImeBackAnimationController implements OnBackAnimationCallback { return mPostCommitAnimator != null && mTriggerBack; } + /** + * Dump information about this ImeBackAnimationController + * + * @param prefix the prefix that will be prepended to each line of the produced output + * @param writer the writer that will receive the resulting text + */ + public void dump(String prefix, PrintWriter writer) { + final String innerPrefix = prefix + " "; + writer.println(prefix + "ImeBackAnimationController:"); + writer.println(innerPrefix + "mLastProgress=" + mLastProgress); + writer.println(innerPrefix + "mTriggerBack=" + mTriggerBack); + writer.println(innerPrefix + "mIsPreCommitAnimationInProgress=" + + mIsPreCommitAnimationInProgress); + writer.println(innerPrefix + "mStartRootScrollY=" + mStartRootScrollY); + writer.println(innerPrefix + "isBackAnimationAllowed=" + isBackAnimationAllowed()); + writer.println(innerPrefix + "isAdjustPan=" + isAdjustPan()); + writer.println(innerPrefix + "isHideAnimationInProgress=" + + isHideAnimationInProgress()); + } + } diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index d22d2a52c8cc..609ad5bb013c 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -78,6 +78,7 @@ public final class InputDevice implements Parcelable { private final InputDeviceIdentifier mIdentifier; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) private final boolean mIsExternal; + @Source private final int mSources; private final int mKeyboardType; private final KeyCharacterMap mKeyCharacterMap; @@ -92,6 +93,7 @@ public final class InputDevice implements Parcelable { private final boolean mHasBattery; private final HostUsiVersion mHostUsiVersion; private final int mAssociatedDisplayId; + private final boolean mEnabled; private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>(); private final ViewBehavior mViewBehavior = new ViewBehavior(this); @@ -359,6 +361,28 @@ public final class InputDevice implements Parcelable { */ public static final int SOURCE_ANY = 0xffffff00; + /** @hide */ + @IntDef(flag = true, prefix = { "SOURCE_" }, value = { + SOURCE_UNKNOWN, + SOURCE_KEYBOARD, + SOURCE_DPAD, + SOURCE_GAMEPAD, + SOURCE_TOUCHSCREEN, + SOURCE_MOUSE, + SOURCE_STYLUS, + SOURCE_BLUETOOTH_STYLUS, + SOURCE_TRACKBALL, + SOURCE_MOUSE_RELATIVE, + SOURCE_TOUCHPAD, + SOURCE_TOUCH_NAVIGATION, + SOURCE_ROTARY_ENCODER, + SOURCE_JOYSTICK, + SOURCE_HDMI, + SOURCE_SENSOR, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Source {} + /** * Constant for retrieving the range of values for {@link MotionEvent#AXIS_X}. * @@ -479,7 +503,7 @@ public final class InputDevice implements Parcelable { int keyboardType, KeyCharacterMap keyCharacterMap, @Nullable String keyboardLanguageTag, @Nullable String keyboardLayoutType, boolean hasVibrator, boolean hasMicrophone, boolean hasButtonUnderPad, boolean hasSensor, boolean hasBattery, int usiVersionMajor, - int usiVersionMinor, int associatedDisplayId) { + int usiVersionMinor, int associatedDisplayId, boolean enabled) { mId = id; mGeneration = generation; mControllerNumber = controllerNumber; @@ -510,6 +534,7 @@ public final class InputDevice implements Parcelable { mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId); mHostUsiVersion = new HostUsiVersion(usiVersionMajor, usiVersionMinor); mAssociatedDisplayId = associatedDisplayId; + mEnabled = enabled; } private InputDevice(Parcel in) { @@ -534,6 +559,7 @@ public final class InputDevice implements Parcelable { mHasBattery = in.readInt() != 0; mHostUsiVersion = HostUsiVersion.CREATOR.createFromParcel(in); mAssociatedDisplayId = in.readInt(); + mEnabled = in.readInt() != 0; mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId); int numRanges = in.readInt(); @@ -578,6 +604,8 @@ public final class InputDevice implements Parcelable { private int mUsiVersionMajor = -1; private int mUsiVersionMinor = -1; private int mAssociatedDisplayId = Display.INVALID_DISPLAY; + // The default is true, the same as the native default state. + private boolean mEnabled = true; private List<MotionRange> mMotionRanges = new ArrayList<>(); private boolean mShouldSmoothScroll; @@ -708,6 +736,12 @@ public final class InputDevice implements Parcelable { return this; } + /** @see InputDevice#isEnabled() */ + public Builder setEnabled(boolean enabled) { + mEnabled = enabled; + return this; + } + /** @see InputDevice#getMotionRanges() */ public Builder addMotionRange(int axis, int source, float min, float max, float flat, float fuzz, float resolution) { @@ -749,7 +783,8 @@ public final class InputDevice implements Parcelable { mHasBattery, mUsiVersionMajor, mUsiVersionMinor, - mAssociatedDisplayId); + mAssociatedDisplayId, + mEnabled); final int numRanges = mMotionRanges.size(); for (int i = 0; i < numRanges; i++) { @@ -1298,7 +1333,7 @@ public final class InputDevice implements Parcelable { * @return Whether the input device is enabled. */ public boolean isEnabled() { - return InputManagerGlobal.getInstance().isInputDeviceEnabled(mId); + return mEnabled; } /** @@ -1588,6 +1623,7 @@ public final class InputDevice implements Parcelable { out.writeInt(mHasBattery ? 1 : 0); mHostUsiVersion.writeToParcel(out, flags); out.writeInt(mAssociatedDisplayId); + out.writeInt(mEnabled ? 1 : 0); int numRanges = mMotionRanges.size(); numRanges = numRanges > MAX_RANGES ? MAX_RANGES : numRanges; @@ -1619,6 +1655,7 @@ public final class InputDevice implements Parcelable { description.append(" Generation: ").append(mGeneration).append("\n"); description.append(" Location: ").append(mIsExternal ? "external" : "built-in").append( "\n"); + description.append(" Enabled: ").append(isEnabled()).append("\n"); description.append(" Keyboard Type: "); switch (mKeyboardType) { diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index de5fc7f3e358..58ef5efe846f 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -67,7 +67,7 @@ public final class InputWindowHandle { InputConfig.SPY, InputConfig.INTERCEPTS_STYLUS, InputConfig.CLONE, - InputConfig.SENSITIVE_FOR_TRACING, + InputConfig.SENSITIVE_FOR_PRIVACY, }) public @interface InputConfigFlags {} diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index c54526747c5c..1fc98cfa7eb4 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -303,7 +303,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** Not running an animation. */ - @VisibleForTesting + @VisibleForTesting(visibility = PACKAGE) public static final int ANIMATION_TYPE_NONE = -1; /** Running animation will show insets */ @@ -317,7 +317,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public static final int ANIMATION_TYPE_USER = 2; /** Running animation will resize insets */ - @VisibleForTesting + @VisibleForTesting(visibility = PACKAGE) public static final int ANIMATION_TYPE_RESIZE = 3; @Retention(RetentionPolicy.SOURCE) @@ -1262,10 +1262,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mHost.getInputMethodManager(), null /* icProto */); } + final var statsToken = (types & ime()) == 0 ? null + : ImeTracker.forLogging().onStart(ImeTracker.TYPE_USER, + ImeTracker.ORIGIN_CLIENT, + SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION, + mHost.isHandlingPointerEvent() /* fromUser */); controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs, interpolator, animationType, getLayoutInsetsDuringAnimationMode(types, fromPredictiveBack), - false /* useInsetsAnimationThread */, null /* statsToken */); + false /* useInsetsAnimationThread */, statsToken); } private void controlAnimationUnchecked(@InsetsType int types, @@ -1567,7 +1572,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return; } final ImeTracker.Token statsToken = runner.getStatsToken(); - if (shown) { + if (runner.getAnimationType() == ANIMATION_TYPE_USER) { + ImeTracker.forLogging().onUserFinished(statsToken, shown); + } else if (shown) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW); ImeTracker.forLogging().onShown(statsToken); @@ -1709,7 +1716,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mImeSourceConsumer.onWindowFocusLost(); } - @VisibleForTesting + @VisibleForTesting(visibility = PACKAGE) public @AnimationType int getAnimationType(@InsetsType int type) { for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner; @@ -1799,8 +1806,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } void dump(String prefix, PrintWriter pw) { - pw.print(prefix); pw.println("InsetsController:"); - mState.dump(prefix + " ", pw); + final String innerPrefix = prefix + " "; + pw.println(prefix + "InsetsController:"); + mState.dump(innerPrefix, pw); + pw.println(innerPrefix + "mIsPredictiveBackImeHideAnimInProgress=" + + mIsPredictiveBackImeHideAnimInProgress); } void dumpDebug(ProtoOutputStream proto, long fieldId) { @@ -1835,6 +1845,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.pendingAnim", 0); mHost.dispatchWindowInsetsAnimationStart(animation, bounds); mStartingAnimation = true; + if (runner.getAnimationType() == ANIMATION_TYPE_USER) { + ImeTracker.forLogging().onDispatched(runner.getStatsToken()); + } runner.setReadyDispatched(true); listener.onReady(runner, types); mStartingAnimation = false; diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index fdb2a6ee1791..6c670f5d6934 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -17,6 +17,7 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; +import static android.view.InsetsController.ANIMATION_TYPE_RESIZE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE; @@ -31,6 +32,7 @@ import static com.android.internal.annotations.VisibleForTesting.Visibility.PACK import android.annotation.IntDef; import android.annotation.Nullable; +import android.graphics.Point; import android.graphics.Rect; import android.util.Log; import android.util.proto.ProtoOutputStream; @@ -179,10 +181,11 @@ public class InsetsSourceConsumer { mController.notifyVisibilityChanged(); } - // If we have a new leash, make sure visibility is up-to-date, even though we - // didn't want to run an animation above. - if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) { - applyRequestedVisibilityToControl(); + // If there is no animation controlling the leash, make sure the visibility and the + // position is up-to-date. + final int animType = mController.getAnimationType(mType); + if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) { + applyRequestedVisibilityAndPositionToControl(); } // Remove the surface that owned by last control when it lost. @@ -371,21 +374,27 @@ public class InsetsSourceConsumer { if (DEBUG) Log.d(TAG, "updateSource: " + newSource); } - private void applyRequestedVisibilityToControl() { - if (mSourceControl == null || mSourceControl.getLeash() == null) { + private void applyRequestedVisibilityAndPositionToControl() { + if (mSourceControl == null) { + return; + } + final SurfaceControl leash = mSourceControl.getLeash(); + if (leash == null) { return; } final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0; + final Point surfacePosition = mSourceControl.getSurfacePosition(); try (Transaction t = mTransactionSupplier.get()) { if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible); if (requestedVisible) { - t.show(mSourceControl.getLeash()); + t.show(leash); } else { - t.hide(mSourceControl.getLeash()); + t.hide(leash); } // Ensure the alpha value is aligned with the actual requested visibility. - t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0); + t.setAlpha(leash, requestedVisible ? 1 : 0); + t.setPosition(leash, surfacePosition.x, surfacePosition.y); t.apply(); } onPerceptible(requestedVisible); diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index 1ee9509b116a..01015ea250e0 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -20,11 +20,13 @@ import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT import static android.view.Display.INVALID_DISPLAY; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; +import android.os.IInputConstants; import android.os.Parcel; import android.os.Parcelable; import android.text.method.MetaKeyKeyListener; @@ -34,6 +36,8 @@ import android.view.KeyCharacterMap.KeyData; import com.android.hardware.input.Flags; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.concurrent.TimeUnit; /** @@ -943,7 +947,7 @@ public class KeyEvent extends InputEvent implements Parcelable { @FlaggedApi(Flags.FLAG_EMOJI_AND_SCREENSHOT_KEYCODES_AVAILABLE) public static final int KEYCODE_SCREENSHOT = 318; - /** + /** * Integer value of the last KEYCODE. Increases as new keycodes are added to KeyEvent. * @hide */ @@ -1033,6 +1037,15 @@ public class KeyEvent extends InputEvent implements Parcelable { @Deprecated public static final int ACTION_MULTIPLE = 2; + /** @hide */ + @IntDef(prefix = {"ACTION_"}, value = { + ACTION_DOWN, + ACTION_UP, + ACTION_MULTIPLE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Action {} + /** * SHIFT key locked in CAPS mode. * Reserved for use by {@link MetaKeyKeyListener} for a published constant in its API. @@ -1221,6 +1234,33 @@ public class KeyEvent extends InputEvent implements Parcelable { */ public static final int META_SCROLL_LOCK_ON = 0x400000; + /** @hide */ + @IntDef(flag = true, prefix = {"META_"}, value = { + META_CAP_LOCKED, + META_ALT_LOCKED, + META_SYM_LOCKED, + META_SELECTING, + META_ALT_ON, + META_ALT_LEFT_ON, + META_ALT_RIGHT_ON, + META_SHIFT_ON, + META_SHIFT_LEFT_ON, + META_SHIFT_RIGHT_ON, + META_SYM_ON, + META_FUNCTION_ON, + META_CTRL_ON, + META_CTRL_LEFT_ON, + META_CTRL_RIGHT_ON, + META_META_ON, + META_META_LEFT_ON, + META_META_RIGHT_ON, + META_CAPS_LOCK_ON, + META_NUM_LOCK_ON, + META_SCROLL_LOCK_ON, + }) + @Retention(RetentionPolicy.SOURCE) + @interface MetaState {} + /** * This mask is a combination of {@link #META_SHIFT_ON}, {@link #META_SHIFT_LEFT_ON} * and {@link #META_SHIFT_RIGHT_ON}. @@ -1295,7 +1335,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * action for a key until it receives an up or the long press timeout has * expired. */ - public static final int FLAG_CANCELED = 0x20; + public static final int FLAG_CANCELED = IInputConstants.INPUT_EVENT_FLAG_CANCELED; /** * This key event was generated by a virtual (on-screen) hard key area. @@ -1363,7 +1403,28 @@ public class KeyEvent extends InputEvent implements Parcelable { * @see #isTainted * @see #setTainted */ - public static final int FLAG_TAINTED = 0x80000000; + public static final int FLAG_TAINTED = IInputConstants.INPUT_EVENT_FLAG_TAINTED; + + /** @hide */ + @IntDef(flag = true, prefix = { "FLAG_" }, value = { + FLAG_WOKE_HERE, + FLAG_SOFT_KEYBOARD, + FLAG_KEEP_TOUCH_MODE, + FLAG_FROM_SYSTEM, + FLAG_EDITOR_ACTION, + FLAG_CANCELED, + FLAG_VIRTUAL_HARD_KEY, + FLAG_LONG_PRESS, + FLAG_CANCELED_LONG_PRESS, + FLAG_TRACKING, + FLAG_FALLBACK, + FLAG_IS_ACCESSIBILITY_EVENT, + FLAG_PREDISPATCH, + FLAG_START_TRACKING, + FLAG_TAINTED, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Flag {} /** * Returns the maximum keycode. @@ -1400,8 +1461,10 @@ public class KeyEvent extends InputEvent implements Parcelable { // NOTE: mHmac is private and not used in this class, but it's used on native side / parcel. private @Nullable byte[] mHmac; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @MetaState private int mMetaState; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @Action private int mAction; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private int mKeyCode; @@ -1410,6 +1473,7 @@ public class KeyEvent extends InputEvent implements Parcelable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private int mRepeatCount; @UnsupportedAppUsage + @Flag private int mFlags; /** * The time when the key initially was pressed, in nanoseconds. Only millisecond precision is diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 56a24e4705b7..6db40bf6e0b8 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -16,7 +16,15 @@ package android.view; +import static android.os.IInputConstants.INPUT_EVENT_FLAG_CANCELED; +import static android.os.IInputConstants.MOTION_EVENT_FLAG_HOVER_EXIT_PENDING; import static android.os.IInputConstants.INPUT_EVENT_FLAG_IS_ACCESSIBILITY_EVENT; +import static android.os.IInputConstants.MOTION_EVENT_FLAG_IS_GENERATED_GESTURE; +import static android.os.IInputConstants.MOTION_EVENT_FLAG_NO_FOCUS_CHANGE; +import static android.os.IInputConstants.INPUT_EVENT_FLAG_TAINTED; +import static android.os.IInputConstants.MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS; +import static android.os.IInputConstants.MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; +import static android.os.IInputConstants.MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; import static android.view.Display.DEFAULT_DISPLAY; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -42,6 +50,7 @@ import dalvik.annotation.optimization.CriticalNative; import dalvik.annotation.optimization.FastNative; import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -437,6 +446,25 @@ public final class MotionEvent extends InputEvent implements Parcelable { @Deprecated public static final int ACTION_POINTER_ID_SHIFT = 8; + /** @hide */ + @IntDef(prefix = { "ACTION_" }, value = { + ACTION_DOWN, + ACTION_UP, + ACTION_MOVE, + ACTION_CANCEL, + ACTION_OUTSIDE, + ACTION_POINTER_DOWN, + ACTION_POINTER_UP, + ACTION_HOVER_MOVE, + ACTION_SCROLL, + ACTION_HOVER_ENTER, + ACTION_HOVER_EXIT, + ACTION_BUTTON_PRESS, + ACTION_BUTTON_RELEASE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface ActionMasked {} + /** * This flag indicates that the window that received this motion event is partly * or wholly obscured by another visible window above it and the event directly passed through @@ -448,7 +476,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * to drop the suspect touches or to take additional precautions to confirm the user's * actual intent. */ - public static final int FLAG_WINDOW_IS_OBSCURED = 0x1; + public static final int FLAG_WINDOW_IS_OBSCURED = MOTION_EVENT_FLAG_WINDOW_IS_OBSCURED; /** * This flag indicates that the window that received this motion event is partly @@ -464,7 +492,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { * Unlike FLAG_WINDOW_IS_OBSCURED, this is only true if the window that received this event is * obstructed in areas other than the touched location. */ - public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 0x2; + public static final int FLAG_WINDOW_IS_PARTIALLY_OBSCURED = + MOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED; /** * This private flag is only set on {@link #ACTION_HOVER_MOVE} events and indicates that @@ -472,7 +501,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * prevent generating redundant {@link #ACTION_HOVER_ENTER} events. * @hide */ - public static final int FLAG_HOVER_EXIT_PENDING = 0x4; + public static final int FLAG_HOVER_EXIT_PENDING = MOTION_EVENT_FLAG_HOVER_EXIT_PENDING; /** * This flag indicates that the event has been generated by a gesture generator. It @@ -480,7 +509,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * * @hide */ - public static final int FLAG_IS_GENERATED_GESTURE = 0x8; + public static final int FLAG_IS_GENERATED_GESTURE = MOTION_EVENT_FLAG_IS_GENERATED_GESTURE; /** * This flag is only set for events with {@link #ACTION_POINTER_UP} and {@link #ACTION_CANCEL}. @@ -493,7 +522,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #ACTION_POINTER_UP * @see #ACTION_CANCEL */ - public static final int FLAG_CANCELED = 0x20; + public static final int FLAG_CANCELED = INPUT_EVENT_FLAG_CANCELED; /** * This flag indicates that the event will not cause a focus change if it is directed to an @@ -502,7 +531,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * window into focus. * @hide */ - public static final int FLAG_NO_FOCUS_CHANGE = 0x40; + public static final int FLAG_NO_FOCUS_CHANGE = MOTION_EVENT_FLAG_NO_FOCUS_CHANGE; /** * This flag indicates that this event was modified by or generated from an accessibility @@ -521,7 +550,7 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #isTainted * @see #setTainted */ - public static final int FLAG_TAINTED = 0x80000000; + public static final int FLAG_TAINTED = INPUT_EVENT_FLAG_TAINTED; /** * Private flag indicating that this event was synthesized by the system and should be delivered @@ -536,7 +565,23 @@ public final class MotionEvent extends InputEvent implements Parcelable { * @see #isTargetAccessibilityFocus() * @see #setTargetAccessibilityFocus(boolean) */ - public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000; + public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = + MOTION_EVENT_FLAG_TARGET_ACCESSIBILITY_FOCUS; + + /** @hide */ + @IntDef(flag = true, prefix = { "FLAG_" }, value = { + FLAG_WINDOW_IS_OBSCURED, + FLAG_WINDOW_IS_PARTIALLY_OBSCURED, + FLAG_HOVER_EXIT_PENDING, + FLAG_IS_GENERATED_GESTURE, + FLAG_CANCELED, + FLAG_NO_FOCUS_CHANGE, + FLAG_IS_ACCESSIBILITY_EVENT, + FLAG_TAINTED, + FLAG_TARGET_ACCESSIBILITY_FOCUS, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Flag {} /** * Flag indicating the motion event intersected the top edge of the screen. @@ -1412,6 +1457,63 @@ public final class MotionEvent extends InputEvent implements Parcelable { names.append(AXIS_GESTURE_SWIPE_FINGER_COUNT, "AXIS_GESTURE_SWIPE_FINGER_COUNT"); } + /** @hide */ + @IntDef(prefix = { "AXIS_" }, value = { + AXIS_X, + AXIS_Y, + AXIS_PRESSURE, + AXIS_SIZE, + AXIS_TOUCH_MAJOR, + AXIS_TOUCH_MINOR, + AXIS_TOOL_MAJOR, + AXIS_TOOL_MINOR, + AXIS_ORIENTATION, + AXIS_VSCROLL, + AXIS_HSCROLL, + AXIS_Z, + AXIS_RX, + AXIS_RY, + AXIS_RZ, + AXIS_HAT_X, + AXIS_HAT_Y, + AXIS_LTRIGGER, + AXIS_RTRIGGER, + AXIS_THROTTLE, + AXIS_RUDDER, + AXIS_WHEEL, + AXIS_GAS, + AXIS_BRAKE, + AXIS_DISTANCE, + AXIS_TILT, + AXIS_SCROLL, + AXIS_RELATIVE_X, + AXIS_RELATIVE_Y, + AXIS_GENERIC_1, + AXIS_GENERIC_2, + AXIS_GENERIC_3, + AXIS_GENERIC_4, + AXIS_GENERIC_5, + AXIS_GENERIC_6, + AXIS_GENERIC_7, + AXIS_GENERIC_8, + AXIS_GENERIC_9, + AXIS_GENERIC_10, + AXIS_GENERIC_11, + AXIS_GENERIC_12, + AXIS_GENERIC_13, + AXIS_GENERIC_14, + AXIS_GENERIC_15, + AXIS_GENERIC_16, + AXIS_GESTURE_X_OFFSET, + AXIS_GESTURE_Y_OFFSET, + AXIS_GESTURE_SCROLL_X_DISTANCE, + AXIS_GESTURE_SCROLL_Y_DISTANCE, + AXIS_GESTURE_PINCH_SCALE_FACTOR, + AXIS_GESTURE_SWIPE_FINGER_COUNT, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Axis {} + /** * Button constant: Primary button (left mouse button). * @@ -1512,6 +1614,19 @@ public final class MotionEvent extends InputEvent implements Parcelable { "0x80000000", }; + /** @hide */ + @IntDef(flag = true, prefix = { "BUTTON_" }, value = { + BUTTON_PRIMARY, + BUTTON_SECONDARY, + BUTTON_TERTIARY, + BUTTON_BACK, + BUTTON_FORWARD, + BUTTON_STYLUS_PRIMARY, + BUTTON_STYLUS_SECONDARY, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Button {} + /** * Classification constant: None. * diff --git a/core/java/android/view/PointerIcon.java b/core/java/android/view/PointerIcon.java index 7dc151d8f9ee..71199e9c3619 100644 --- a/core/java/android/view/PointerIcon.java +++ b/core/java/android/view/PointerIcon.java @@ -234,7 +234,7 @@ public final class PointerIcon implements Parcelable { } int typeIndex = getSystemIconTypeIndex(type); - if (typeIndex == 0) { + if (typeIndex < 0) { typeIndex = getSystemIconTypeIndex(TYPE_DEFAULT); } @@ -606,7 +606,7 @@ public final class PointerIcon implements Parcelable { case TYPE_HANDWRITING: return com.android.internal.R.styleable.Pointer_pointerIconHandwriting; default: - return 0; + return -1; } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index a23df799da59..1d70d18ac4c8 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -986,7 +986,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall updateBackgroundVisibility(surfaceUpdateTransaction); updateBackgroundColor(surfaceUpdateTransaction); - if (mLimitedHdrEnabled && hdrHeadroomChanged) { + if (mLimitedHdrEnabled && (hdrHeadroomChanged || creating)) { surfaceUpdateTransaction.setDesiredHdrHeadroom( mBlastSurfaceControl, mHdrHeadroom); } diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index d31f82398cdf..27176a4c2094 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -264,7 +264,6 @@ public final class VelocityTracker { /** * Obtains a velocity tracker with the specified strategy. - * For testing and comparison purposes only. * * @param strategy The strategy Id, VELOCITY_TRACKER_STRATEGY_DEFAULT to use the default. * @return The velocity tracker. @@ -272,6 +271,9 @@ public final class VelocityTracker { * @hide */ public static VelocityTracker obtain(int strategy) { + if (strategy == VELOCITY_TRACKER_STRATEGY_DEFAULT) { + return obtain(); + } return new VelocityTracker(strategy); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 9579614ac379..1cb276568244 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -40,7 +40,6 @@ import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY; import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API; import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout; import static android.view.flags.Flags.sensitiveContentAppProtection; -import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix; import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly; import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly; import static android.view.flags.Flags.toolkitFrameRateSmallUsesPercentReadOnly; @@ -30696,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(); } /** @@ -32230,7 +32219,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void increaseSensitiveViewsCount() { if (mSensitiveViewsCount == 0) { - mViewRootImpl.notifySensitiveContentAppProtection(true); + mViewRootImpl.addSensitiveContentAppProtection(); } mSensitiveViewsCount++; } @@ -32238,11 +32227,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, void decreaseSensitiveViewsCount() { mSensitiveViewsCount--; if (mSensitiveViewsCount == 0) { - if (sensitiveContentPrematureProtectionRemovedFix()) { - mViewRootImpl.removeSensitiveContentProtectionOnTransactionCommit(); - } else { - mViewRootImpl.notifySensitiveContentAppProtection(false); - } + mViewRootImpl.removeSensitiveContentAppProtection(); } if (mSensitiveViewsCount < 0) { Log.wtf(VIEW_LOG_TAG, "mSensitiveViewsCount is negative" + mSensitiveViewsCount); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1d843757489e..0715474f1c13 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -25,6 +25,7 @@ import static android.os.Trace.TRACE_TAG_VIEW; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.DragEvent.ACTION_DRAG_LOCATION; +import static android.view.flags.Flags.sensitiveContentPrematureProtectionRemovedFix; import static android.view.InputDevice.SOURCE_CLASS_NONE; import static android.view.InsetsSource.ID_IME; import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT; @@ -73,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; @@ -95,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; @@ -121,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; @@ -194,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; @@ -267,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; @@ -916,15 +921,6 @@ public final class ViewRootImpl implements ViewParent, private String mFrameRateCategoryView; /** - * The resolved pointer icon type requested by this window. - * A null value indicates the resolved pointer icon has not yet been calculated. - */ - // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete. - @Nullable - private Integer mPointerIconType = null; - private PointerIcon mCustomPointerIcon = null; - - /** * The resolved pointer icon requested by this window. * A null value indicates the resolved pointer icon has not yet been calculated. */ @@ -4244,7 +4240,14 @@ public final class ViewRootImpl implements ViewParent, mReportNextDraw = false; mLastReportNextDrawReason = null; mActiveSurfaceSyncGroup = null; - mHasPendingTransactions = false; + if (mHasPendingTransactions) { + // TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't + // merged with a sync group or BLASTBufferQueue before making it to this point + // But better a one or two frame flicker than steady-state broken from dropping + // whatever is in this transaction + mPendingTransaction.apply(); + mHasPendingTransactions = false; + } mSyncBuffer = false; if (isInWMSRequestedSync()) { mWmsRequestSyncGroup.markSyncReady(); @@ -4331,29 +4334,42 @@ public final class ViewRootImpl implements ViewParent, * <li>It should only notify service to unblock projection when all sensitive view are * removed from the window. * </ol> + * + * @param enableProtection if true, the protection is enabled for this window. + * if false, the protection is removed for this window. */ - void notifySensitiveContentAppProtection(boolean showSensitiveContent) { + private void applySensitiveContentAppProtection(boolean enableProtection) { try { if (mSensitiveContentProtectionService == null) { return; } if (DEBUG_SENSITIVE_CONTENT) { Log.d(TAG, "Notify sensitive content, package=" + mContext.getPackageName() - + ", token=" + getWindowToken() + ", flag=" + showSensitiveContent); + + ", token=" + getWindowToken() + ", flag=" + enableProtection); } // The window would be blocked during screen share if it shows sensitive content. mSensitiveContentProtectionService.setSensitiveContentProtection( - getWindowToken(), mContext.getPackageName(), showSensitiveContent); + getWindowToken(), mContext.getPackageName(), enableProtection); } catch (RemoteException ex) { Log.e(TAG, "Unable to protect sensitive content during screen share", ex); } } /** - * Sensitive protection is removed on transaction commit to avoid prematurely removing - * the protection. + * Add sensitive content protection, when there are one or more visible sensitive views. + */ + void addSensitiveContentAppProtection() { + applySensitiveContentAppProtection(true); + } + + /** + * Remove sensitive content protection, when there is no visible sensitive view. */ - void removeSensitiveContentProtectionOnTransactionCommit() { + void removeSensitiveContentAppProtection() { + if (!sensitiveContentPrematureProtectionRemovedFix()) { + applySensitiveContentAppProtection(false); + return; + } if (DEBUG_SENSITIVE_CONTENT) { Log.d(TAG, "Add transaction to remove sensitive content protection, package=" + mContext.getPackageName() + ", token=" + getWindowToken()); @@ -4361,7 +4377,7 @@ public final class ViewRootImpl implements ViewParent, Transaction t = new Transaction(); t.addTransactionCommittedListener(mExecutor, () -> { if (mAttachInfo.mSensitiveViewsCount == 0) { - notifySensitiveContentAppProtection(false); + applySensitiveContentAppProtection(false); } }); applyTransactionOnDraw(t); @@ -5279,10 +5295,12 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_CONTENT_CAPTURE) { Log.v(mTag, "performContentCaptureInitialReport() on " + rootView); } + boolean traceDispatchCapture = false; try { if (!isContentCaptureEnabled()) return; - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + traceDispatchCapture = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW); + if (traceDispatchCapture) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchContentCapture() for " + getClass().getSimpleName()); } @@ -5298,7 +5316,9 @@ public final class ViewRootImpl implements ViewParent, // Content capture is a go! rootView.dispatchInitialProvideContentCaptureStructure(); } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + if (traceDispatchCapture) { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } } @@ -5306,10 +5326,12 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_CONTENT_CAPTURE) { Log.v(mTag, "handleContentCaptureFlush()"); } + boolean traceFlushContentCapture = false; try { if (!isContentCaptureEnabled()) return; - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + traceFlushContentCapture = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW); + if (traceFlushContentCapture) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "flushContentCapture for " + getClass().getSimpleName()); } @@ -5321,7 +5343,9 @@ public final class ViewRootImpl implements ViewParent, } ccm.flush(ContentCaptureSession.FLUSH_REASON_VIEW_ROOT_ENTERED); } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + if (traceFlushContentCapture) { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } } @@ -6397,7 +6421,6 @@ public final class ViewRootImpl implements ViewParent, private static final int MSG_SYNTHESIZE_INPUT_EVENT = 24; private static final int MSG_DISPATCH_WINDOW_SHOWN = 25; private static final int MSG_REQUEST_KEYBOARD_SHORTCUTS = 26; - private static final int MSG_UPDATE_POINTER_ICON = 27; private static final int MSG_POINTER_CAPTURE_CHANGED = 28; private static final int MSG_INSETS_CONTROL_CHANGED = 29; private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 30; @@ -6462,8 +6485,6 @@ public final class ViewRootImpl implements ViewParent, return "MSG_SYNTHESIZE_INPUT_EVENT"; case MSG_DISPATCH_WINDOW_SHOWN: return "MSG_DISPATCH_WINDOW_SHOWN"; - case MSG_UPDATE_POINTER_ICON: - return "MSG_UPDATE_POINTER_ICON"; case MSG_POINTER_CAPTURE_CHANGED: return "MSG_POINTER_CAPTURE_CHANGED"; case MSG_INSETS_CONTROL_CHANGED: @@ -6714,10 +6735,6 @@ public final class ViewRootImpl implements ViewParent, final int deviceId = msg.arg1; handleRequestKeyboardShortcuts(receiver, deviceId); } break; - case MSG_UPDATE_POINTER_ICON: { - MotionEvent event = (MotionEvent) msg.obj; - resetPointerIcon(event); - } break; case MSG_POINTER_CAPTURE_CHANGED: { final boolean hasCapture = msg.arg1 != 0; handlePointerCaptureChanged(hasCapture); @@ -7771,6 +7788,7 @@ public final class ViewRootImpl implements ViewParent, mWindowAttributes.type)) { // set the frame rate to the maximum value. mIsTouchBoosting = true; + setPreferredFrameRateCategory(mLastPreferredFrameRateCategory); } /** * We want to lower the refresh rate when MotionEvent.ACTION_UP, @@ -7802,14 +7820,12 @@ public final class ViewRootImpl implements ViewParent, || action == MotionEvent.ACTION_HOVER_EXIT) { // Other apps or the window manager may change the icon type outside of // this app, therefore the icon type has to be reset on enter/exit event. - mPointerIconType = null; mResolvedPointerIcon = null; } if (action != MotionEvent.ACTION_HOVER_EXIT) { // Resolve the pointer icon if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) { - mPointerIconType = null; mResolvedPointerIcon = null; } } @@ -7872,12 +7888,6 @@ public final class ViewRootImpl implements ViewParent, return mAttachInfo.mHandlingPointerEvent; } - private void resetPointerIcon(MotionEvent event) { - mPointerIconType = null; - mResolvedPointerIcon = null; - updatePointerIcon(event); - } - /** * If there is pointer that is showing a PointerIcon in this window, refresh the icon for that @@ -7907,46 +7917,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; } @@ -8563,48 +8547,55 @@ public final class ViewRootImpl implements ViewParent, private int mPendingKeyMetaState; - private final GestureDetector mGestureDetector = new GestureDetector(mContext, - new GestureDetector.OnGestureListener() { - @Override - public boolean onDown(@NonNull MotionEvent e) { - // This can be ignored since it's not clear what KeyEvent this will - // belong to. - return true; - } - - @Override - public void onShowPress(@NonNull MotionEvent e) { + private final GestureDetector mGestureDetector; - } + SyntheticTouchNavigationHandler() { + super(true); + int gestureDetectorVelocityStrategy = + android.companion.virtual.flags.Flags + .impulseVelocityStrategyForTouchNavigation() + ? VelocityTracker.VELOCITY_TRACKER_STRATEGY_IMPULSE + : VelocityTracker.VELOCITY_TRACKER_STRATEGY_DEFAULT; + mGestureDetector = new GestureDetector(mContext, + new GestureDetector.OnGestureListener() { + @Override + public boolean onDown(@NonNull MotionEvent e) { + // This can be ignored since it's not clear what KeyEvent this will + // belong to. + return true; + } - @Override - public boolean onSingleTapUp(@NonNull MotionEvent e) { - dispatchTap(e.getEventTime()); - return true; - } + @Override + public void onShowPress(@NonNull MotionEvent e) { + } - @Override - public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, - float distanceX, float distanceY) { - // Scroll doesn't translate to DPAD events so should be ignored. - return true; - } + @Override + public boolean onSingleTapUp(@NonNull MotionEvent e) { + dispatchTap(e.getEventTime()); + return true; + } - @Override - public void onLongPress(@NonNull MotionEvent e) { - // Long presses don't translate to DPAD events so should be ignored. - } + @Override + public boolean onScroll(@Nullable MotionEvent e1, @NonNull MotionEvent e2, + float distanceX, float distanceY) { + // Scroll doesn't translate to DPAD events so should be ignored. + return true; + } - @Override - public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, - float velocityX, float velocityY) { - dispatchFling(velocityX, velocityY, e2.getEventTime()); - return true; - } - }); + @Override + public void onLongPress(@NonNull MotionEvent e) { + // Long presses don't translate to DPAD events so should be ignored. + } - SyntheticTouchNavigationHandler() { - super(true); + @Override + public boolean onFling(@Nullable MotionEvent e1, @NonNull MotionEvent e2, + float velocityX, float velocityY) { + dispatchFling(velocityX, velocityY, e2.getEventTime()); + return true; + } + }, + /* handler= */ null, + gestureDetectorVelocityStrategy); } public void process(MotionEvent event) { @@ -9579,6 +9570,8 @@ public final class ViewRootImpl implements ViewParent, mOnBackInvokedDispatcher.dump(prefix, writer); + mImeBackAnimationController.dump(prefix, writer); + writer.println(prefix + "View Hierarchy:"); dumpViewHierarchy(innerPrefix, writer, mView); } @@ -10548,16 +10541,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. @@ -11421,14 +11404,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) { @@ -11451,6 +11426,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 { @@ -12635,10 +12630,12 @@ public final class ViewRootImpl implements ViewParent, view = mFrameRateCategoryView; } + boolean traceFrameRateCategory = false; try { if (frameRateCategory != FRAME_RATE_CATEGORY_DEFAULT && mLastPreferredFrameRateCategory != frameRateCategory) { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + traceFrameRateCategory = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW); + if (traceFrameRateCategory) { String reason = reasonToString(frameRateReason); String sourceView = view == null ? "-" : view; String category = categoryToString(frameRateCategory); @@ -12656,7 +12653,9 @@ public final class ViewRootImpl implements ViewParent, } catch (Exception e) { Log.e(mTag, "Unable to set frame rate category", e); } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + if (traceFrameRateCategory) { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } } @@ -12696,9 +12695,11 @@ public final class ViewRootImpl implements ViewParent, return; } + boolean traceFrameRate = false; try { if (mLastPreferredFrameRate != preferredFrameRate) { - if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + traceFrameRate = Trace.isTagEnabled(Trace.TRACE_TAG_VIEW); + if (traceFrameRate) { Trace.traceBegin( Trace.TRACE_TAG_VIEW, "ViewRootImpl#setFrameRate " + preferredFrameRate + " compatibility " @@ -12713,7 +12714,9 @@ public final class ViewRootImpl implements ViewParent, } catch (Exception e) { Log.e(mTag, "Unable to set frame rate", e); } finally { - Trace.traceEnd(Trace.TRACE_TAG_VIEW); + if (traceFrameRate) { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } } } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 0bc2430f8805..0f54940ba0e5 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -781,7 +781,7 @@ public interface WindowManager extends ViewManager { * <p> * The metrics describe the size of the area the window would occupy with * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets} - * such a window would have. + * such a window would have. The {@link WindowInsets} are not deducted from the bounds. * <p> * The value of this is based on the <b>current</b> windowing state of the system. * @@ -811,7 +811,7 @@ public interface WindowManager extends ViewManager { * <p> * The metrics describe the size of the largest potential area the window might occupy with * {@link LayoutParams#MATCH_PARENT MATCH_PARENT} width and height, and the {@link WindowInsets} - * such a window would have. + * such a window would have. The {@link WindowInsets} are not deducted from the bounds. * <p> * Note that this might still be smaller than the size of the physical display if certain areas * of the display are not available to windows created in this {@link Context}. @@ -4264,11 +4264,9 @@ public interface WindowManager extends ViewManager { * no letterbox is applied."/> * * <p> - * A cutout in the corner is considered to be on the short edge: <br/> - * <img src="{@docRoot}reference/android/images/display_cutout/short_edge/fullscreen_corner_no_letterbox.png" - * height="720" - * alt="Screenshot of a fullscreen activity on a display with a cutout in the corner in - * portrait, no letterbox is applied."/> + * A cutout in the corner can be considered to be on different edge in different device + * rotations. This behavior may vary from device to device. Use this flag is possible to + * letterbox your app if the display cutout is at corner. * * <p> * On the other hand, should the cutout be on the long edge of the display, a letterbox will @@ -4365,7 +4363,8 @@ public interface WindowManager extends ViewManager { public static final int INPUT_FEATURE_SPY = 1 << 2; /** - * Input feature used to indicate that this window is sensitive for tracing. + * Input feature used to indicate that this window is privacy sensitive. This may be used + * to redact input interactions from tracing or screen mirroring. * <p> * A window that uses {@link LayoutParams#FLAG_SECURE} will automatically be treated as * a sensitive for input tracing, but this input feature can be set on windows that don't @@ -4378,7 +4377,7 @@ public interface WindowManager extends ViewManager { * * @hide */ - public static final int INPUT_FEATURE_SENSITIVE_FOR_TRACING = 1 << 3; + public static final int INPUT_FEATURE_SENSITIVE_FOR_PRIVACY = 1 << 3; /** * An internal annotation for flags that can be specified to {@link #inputFeatures}. @@ -4392,7 +4391,7 @@ public interface WindowManager extends ViewManager { INPUT_FEATURE_NO_INPUT_CHANNEL, INPUT_FEATURE_DISABLE_USER_ACTIVITY, INPUT_FEATURE_SPY, - INPUT_FEATURE_SENSITIVE_FOR_TRACING, + INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, }) public @interface InputFeatureFlags { } diff --git a/core/java/android/view/WindowMetrics.java b/core/java/android/view/WindowMetrics.java index 26298bc645ad..8bcc9de118e2 100644 --- a/core/java/android/view/WindowMetrics.java +++ b/core/java/android/view/WindowMetrics.java @@ -101,9 +101,13 @@ public final class WindowMetrics { * Returns the bounds of the area associated with this window or {@code UiContext}. * <p> * <b>Note that the size of the reported bounds can have different size than - * {@link Display#getSize(Point)}.</b> This method reports the window size including all system - * bar areas, while {@link Display#getSize(Point)} reports the area excluding navigation bars - * and display cutout areas. The value reported by {@link Display#getSize(Point)} can be + * {@link Display#getSize(Point)} based on your target API level and calling context.</b> + * This method reports the window size including all system + * bar areas, while {@link Display#getSize(Point)} can report the area excluding navigation bars + * and display cutout areas depending on the calling context and target SDK level. Please refer + * to {@link Display#getSize(Point)} for details. + * <p> + * The value reported by {@link Display#getSize(Point)} excluding system decoration areas can be * obtained by using: * <pre class="prettyprint"> * final WindowMetrics metrics = windowManager.getCurrentWindowMetrics(); 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/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index da2bf9d7ab38..4de3a7b5ae4b 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -124,6 +124,7 @@ flag { namespace: "accessibility" name: "add_type_window_control" is_exported: true + is_fixed_read_only: true description: "adds new TYPE_WINDOW_CONTROL to AccessibilityWindowInfo for detecting Window Decorations" bug: "320445550" } diff --git a/core/java/android/view/animation/AnimationSet.java b/core/java/android/view/animation/AnimationSet.java index a2f3544c70ab..5aaa994f3f8f 100644 --- a/core/java/android/view/animation/AnimationSet.java +++ b/core/java/android/view/animation/AnimationSet.java @@ -374,7 +374,7 @@ public class AnimationSet extends Animation { final Animation a = mAnimations.get(i); temp.clear(); - a.getTransformationAt(interpolatedTime, t); + a.getTransformationAt(interpolatedTime, temp); t.compose(temp); } } diff --git a/core/java/android/view/animation/Transformation.java b/core/java/android/view/animation/Transformation.java index de31667c96cb..812ecd158b18 100644 --- a/core/java/android/view/animation/Transformation.java +++ b/core/java/android/view/animation/Transformation.java @@ -78,6 +78,7 @@ public class Transformation { mHasClipRect = false; mAlpha = 1.0f; mTransformationType = TYPE_BOTH; + mInsets = Insets.NONE; } /** diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index c7df15c7d5fa..9cc4191d0c80 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -1592,7 +1592,8 @@ public final class AutofillManager { // request comes in but PCC Detection hasn't been triggered. There is no benefit to // trigger PCC Detection separately in those cases. if (!isActiveLocked()) { - final boolean clientAdded = tryAddServiceClientIfNeededLocked(); + final boolean clientAdded = + tryAddServiceClientIfNeededLocked(isCredmanRequested); if (clientAdded) { startSessionLocked(/* id= */ AutofillId.NO_AUTOFILL_ID, /* bounds= */ null, /* value= */ null, /* flags= */ FLAG_PCC_DETECTION); @@ -1850,7 +1851,8 @@ public final class AutofillManager { Rect bounds, AutofillValue value, int flags) { if (shouldIgnoreViewEnteredLocked(id, flags)) return null; - final boolean clientAdded = tryAddServiceClientIfNeededLocked(); + boolean credmanRequested = isCredmanRequested(view); + final boolean clientAdded = tryAddServiceClientIfNeededLocked(credmanRequested); if (!clientAdded) { if (sVerbose) Log.v(TAG, "ignoring notifyViewEntered(" + id + "): no service client"); return null; @@ -1976,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); @@ -2645,6 +2654,11 @@ public final class AutofillManager { */ @GuardedBy("mLock") private boolean tryAddServiceClientIfNeededLocked() { + return tryAddServiceClientIfNeededLocked(/*credmanRequested=*/ false); + } + + @GuardedBy("mLock") + private boolean tryAddServiceClientIfNeededLocked(boolean credmanRequested) { final AutofillClient client = getClient(); if (client == null) { return false; @@ -2659,7 +2673,7 @@ public final class AutofillManager { final int userId = mContext.getUserId(); final SyncResultReceiver receiver = new SyncResultReceiver(SYNC_CALLS_TIMEOUT_MS); mService.addClient(mServiceClient, client.autofillClientGetComponentName(), - userId, receiver); + userId, receiver, credmanRequested); int flags = 0; try { flags = receiver.getIntResult(); @@ -2971,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 cefd6dc13005..2039b4d5c1bd 100644 --- a/core/java/android/view/autofill/IAutoFillManager.aidl +++ b/core/java/android/view/autofill/IAutoFillManager.aidl @@ -38,7 +38,7 @@ import com.android.internal.os.IResultReceiver; oneway interface IAutoFillManager { // Returns flags: FLAG_ADD_CLIENT_ENABLED | FLAG_ADD_CLIENT_DEBUG | FLAG_ADD_CLIENT_VERBOSE void addClient(in IAutoFillManagerClient client, in ComponentName componentName, int userId, - in IResultReceiver result); + in IResultReceiver result, boolean credmanRequested); void removeClient(in IAutoFillManagerClient client, int userId); void startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId, in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags, @@ -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/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig index 12bd45af7ffb..c0d31fae4b8f 100644 --- a/core/java/android/view/flags/view_flags.aconfig +++ b/core/java/android/view/flags/view_flags.aconfig @@ -54,7 +54,7 @@ flag { is_fixed_read_only: true metadata { purpose: PURPOSE_BUGFIX - } + } } flag { 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/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index f454a6abf6a0..3091bf48a09e 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -754,6 +754,19 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onDispatched */ + static void onDispatched(@NonNull ImeTracker.Token statsToken) { + final IImeTracker service = getImeTrackerService(); + if (service == null) { + return; + } + try { + service.onDispatched(statsToken); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */ @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index d992febc375e..edc99218c71e 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -71,24 +71,40 @@ public interface ImeTracker { /** The type of the IME request. */ @IntDef(prefix = { "TYPE_" }, value = { TYPE_SHOW, - TYPE_HIDE + TYPE_HIDE, + TYPE_USER, }) @Retention(RetentionPolicy.SOURCE) @interface Type {} - /** IME show request type. */ + /** + * IME show request type. + * + * @see android.view.InsetsController#ANIMATION_TYPE_SHOW + */ int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW; - /** IME hide request type. */ + /** + * IME hide request type. + * + * @see android.view.InsetsController#ANIMATION_TYPE_HIDE + */ int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE; + /** + * IME user-controlled animation request type. + * + * @see android.view.InsetsController#ANIMATION_TYPE_USER + */ + int TYPE_USER = ImeProtoEnums.TYPE_USER; + /** The status of the IME request. */ @IntDef(prefix = { "STATUS_" }, value = { STATUS_RUN, STATUS_CANCEL, STATUS_FAIL, STATUS_SUCCESS, - STATUS_TIMEOUT + STATUS_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) @interface Status {} @@ -117,7 +133,7 @@ public interface ImeTracker { @IntDef(prefix = { "ORIGIN_" }, value = { ORIGIN_CLIENT, ORIGIN_SERVER, - ORIGIN_IME + ORIGIN_IME, }) @Retention(RetentionPolicy.SOURCE) @interface Origin {} @@ -400,20 +416,36 @@ public interface ImeTracker { void onCancelled(@Nullable Token token, @Phase int phase); /** - * Called when the IME show request is successful. + * Called when the show IME request is successful. * * @param token the token tracking the current IME request or {@code null} otherwise. */ void onShown(@Nullable Token token); /** - * Called when the IME hide request is successful. + * Called when the hide IME request is successful. * * @param token the token tracking the current IME request or {@code null} otherwise. */ void onHidden(@Nullable Token token); /** + * Called when the user-controlled IME request was dispatched to the requesting app. The + * user animation can take an undetermined amount of time, so it shouldn't be tracked. + * + * @param token the token tracking the current IME request or {@code null} otherwise. + */ + void onDispatched(@Nullable Token token); + + /** + * Called when the animation of the user-controlled IME request finished. + * + * @param token the token tracking the current IME request or {@code null} otherwise. + * @param shown whether the end state of the animation was shown or hidden. + */ + void onUserFinished(@Nullable Token token, boolean shown); + + /** * Returns whether the current IME request was created due to a user interaction. This can * only be {@code true} when running on the view's UI thread. * @@ -482,13 +514,6 @@ public interface ImeTracker { /** Whether the stack trace at the request call site should be logged. */ private boolean mLogStackTrace; - private void reloadSystemProperties() { - mLogProgress = SystemProperties.getBoolean( - "persist.debug.imetracker", false); - mLogStackTrace = SystemProperties.getBoolean( - "persist.debug.imerequest.logstacktrace", false); - } - @NonNull @Override public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin, @@ -497,7 +522,7 @@ public interface ImeTracker { final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type, origin, reason, fromUser); - Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide") + Log.i(TAG, token.mTag + ": " + getOnStartPrefix(type) + " at " + Debug.originToString(origin) + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason) + " fromUser " + fromUser, @@ -552,6 +577,45 @@ public interface ImeTracker { Log.i(TAG, token.mTag + ": onHidden"); } + + @Override + public void onDispatched(@Nullable Token token) { + if (token == null) return; + IInputMethodManagerGlobalInvoker.onDispatched(token); + + Log.i(TAG, token.mTag + ": onDispatched"); + } + + @Override + public void onUserFinished(@Nullable Token token, boolean shown) { + if (token == null) return; + // This is already sent to ImeTrackerService to mark it finished during onDispatched. + + Log.i(TAG, token.mTag + ": onUserFinished " + (shown ? "shown" : "hidden")); + } + + /** + * Gets the prefix string for {@link #onStart} based on the given request type. + * + * @param type request type for which to create the prefix string with. + */ + @NonNull + private static String getOnStartPrefix(@Type int type) { + return switch (type) { + case TYPE_SHOW -> "onRequestShow"; + case TYPE_HIDE -> "onRequestHide"; + case TYPE_USER -> "onRequestUser"; + default -> "onRequestUnknown"; + }; + } + + /** Reloads the system properties related to this class. */ + private void reloadSystemProperties() { + mLogProgress = SystemProperties.getBoolean( + "persist.debug.imetracker", false); + mLogStackTrace = SystemProperties.getBoolean( + "persist.debug.imerequest.logstacktrace", false); + } }; /** The singleton IME tracker instance for instrumenting jank metrics. */ diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 7885cd95ca25..11ee286652a1 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -54,6 +54,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -431,6 +432,14 @@ public final class InputMethodInfo implements Parcelable { * @hide */ public InputMethodInfo(InputMethodInfo source) { + this(source, Collections.emptyList()); + } + + /** + * @hide + */ + public InputMethodInfo(@NonNull InputMethodInfo source, + @NonNull List<InputMethodSubtype> additionalSubtypes) { mId = source.mId; mSettingsActivityName = source.mSettingsActivityName; mLanguageSettingsActivityName = source.mLanguageSettingsActivityName; @@ -445,7 +454,19 @@ public final class InputMethodInfo implements Parcelable { mIsVrOnly = source.mIsVrOnly; mIsVirtualDeviceOnly = source.mIsVirtualDeviceOnly; mService = source.mService; - mSubtypes = source.mSubtypes; + if (additionalSubtypes.isEmpty()) { + mSubtypes = source.mSubtypes; + } else { + final ArrayList<InputMethodSubtype> subtypes = source.mSubtypes.toList(); + final int additionalSubtypeCount = additionalSubtypes.size(); + for (int i = 0; i < additionalSubtypeCount; ++i) { + final InputMethodSubtype additionalSubtype = additionalSubtypes.get(i); + if (!subtypes.contains(additionalSubtype)) { + subtypes.add(additionalSubtype); + } + } + mSubtypes = new InputMethodSubtypeArray(subtypes); + } mHandledConfigChanges = source.mHandledConfigChanges; mSupportsStylusHandwriting = source.mSupportsStylusHandwriting; mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting; diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 1cdcd2015f03..cf128fbaf50f 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2461,24 +2461,25 @@ public final class InputMethodManager { * @hide */ public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) { + checkFocus(); final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused(); synchronized (mH) { - if (!isFocusedAndWindowFocused && !hasServedByInputMethodLocked(view)) { + final boolean hasServedByInputMethod = hasServedByInputMethodLocked(view); + if (!isFocusedAndWindowFocused && !hasServedByInputMethod) { // Fail early if the view is not focused and not served // to avoid logging many erroneous calls. return false; } - } - final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW; - final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, - ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view)); - ImeTracker.forLatency().onRequestHide(statsToken, - ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); - ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView", - this, null /* icProto */); - synchronized (mH) { - if (!hasServedByInputMethodLocked(view)) { + final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW; + final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE, + ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view)); + ImeTracker.forLatency().onRequestHide(statsToken, + ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication); + ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView", + this, null /* icProto */); + + if (!hasServedByInputMethod) { ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); ImeTracker.forLatency().onShowFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication); @@ -2529,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) { @@ -2890,7 +2879,7 @@ public final class InputMethodManager { if (Flags.homeScreenHandwritingDelegator()) { flags = delegateView.getHandwritingDelegateFlags(); } - startStylusHandwritingInternalAsync( + acceptStylusHandwritingDelegation( delegateView, delegatorPackageName, flags, executor, callback); } @@ -2925,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/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java index c243a22b27b1..e2d343f45e5d 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java +++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java @@ -25,6 +25,7 @@ import android.util.Slog; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -163,6 +164,18 @@ public class InputMethodSubtypeArray { } /** + * @return A list of {@link InputMethodInfo} copied from this array. + */ + @NonNull + public ArrayList<InputMethodSubtype> toList() { + final ArrayList<InputMethodSubtype> list = new ArrayList<>(mCount); + for (int i = 0; i < mCount; ++i) { + list.add(get(i)); + } + return list; + } + + /** * Return the number of {@link InputMethodSubtype} objects. */ public int getCount() { 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/widget/flags/notification_widget_flags.aconfig b/core/java/android/widget/flags/notification_widget_flags.aconfig index 3a39631f8c81..503e542f715a 100644 --- a/core/java/android/widget/flags/notification_widget_flags.aconfig +++ b/core/java/android/widget/flags/notification_widget_flags.aconfig @@ -47,3 +47,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "messaging_child_request_layout" + namespace: "systemui" + description: "MessagingChild always needs to be measured during MessagingLinearLayout onMeasure." + bug: "324537506" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java index 163e43a009e7..6fe0784a2fa5 100644 --- a/core/java/android/window/BackProgressAnimator.java +++ b/core/java/android/window/BackProgressAnimator.java @@ -114,7 +114,6 @@ public class BackProgressAnimator { * dispatches as the progress animation updates. */ public void onBackStarted(BackMotionEvent event, ProgressCallback callback) { - reset(); mLastBackEvent = event; mCallback = callback; mBackAnimationInProgress = true; diff --git a/core/java/android/window/RemoteTransition.java b/core/java/android/window/RemoteTransition.java index 4cc7ec598dbf..15b3c4490f94 100644 --- a/core/java/android/window/RemoteTransition.java +++ b/core/java/android/window/RemoteTransition.java @@ -22,15 +22,12 @@ import android.app.IApplicationThread; import android.os.IBinder; import android.os.Parcelable; -import com.android.internal.util.DataClass; - /** * Represents a remote transition animation and information required to run it (eg. the app thread * that needs to be boosted). * @hide */ -@DataClass(genToString = true, genSetters = true, genAidl = true) -public class RemoteTransition implements Parcelable { +public final class RemoteTransition implements Parcelable { /** The actual remote-transition interface used to run the transition animation. */ private @NonNull IRemoteTransition mRemoteTransition; @@ -41,12 +38,18 @@ public class RemoteTransition implements Parcelable { /** A name for this that can be used for debugging. */ private @Nullable String mDebugName; - /** Constructs with no app thread (animation runs in shell). */ + /** + * Constructs with no app thread (animation runs in shell). + * @hide + */ public RemoteTransition(@NonNull IRemoteTransition remoteTransition) { this(remoteTransition, null /* appThread */, null /* debugName */); } - /** Constructs with no app thread (animation runs in shell). */ + /** + * Constructs with no app thread (animation runs in shell). + * @hide + */ public RemoteTransition(@NonNull IRemoteTransition remoteTransition, @Nullable String debugName) { this(remoteTransition, null /* appThread */, debugName); @@ -57,21 +60,6 @@ public class RemoteTransition implements Parcelable { return mRemoteTransition.asBinder(); } - - - // Code below generated by codegen v1.0.23. - // - // DO NOT MODIFY! - // CHECKSTYLE:OFF Generated code - // - // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/window/RemoteTransition.java - // - // To exclude the generated code from IntelliJ auto-formatting enable (one-time): - // Settings > Editor > Code Style > Formatter Control - //@formatter:off - - /** * Creates a new RemoteTransition. * @@ -81,8 +69,8 @@ public class RemoteTransition implements Parcelable { * The application thread that will be running the remote transition. * @param debugName * A name for this that can be used for debugging. + * @hide */ - @DataClass.Generated.Member public RemoteTransition( @NonNull IRemoteTransition remoteTransition, @Nullable IApplicationThread appThread, @@ -98,16 +86,16 @@ public class RemoteTransition implements Parcelable { /** * The actual remote-transition interface used to run the transition animation. + * @hide */ - @DataClass.Generated.Member public @NonNull IRemoteTransition getRemoteTransition() { return mRemoteTransition; } /** * The application thread that will be running the remote transition. + * @hide */ - @DataClass.Generated.Member public @Nullable IApplicationThread getAppThread() { return mAppThread; } @@ -115,15 +103,14 @@ public class RemoteTransition implements Parcelable { /** * A name for this that can be used for debugging. */ - @DataClass.Generated.Member public @Nullable String getDebugName() { return mDebugName; } /** * The actual remote-transition interface used to run the transition animation. + * @hide */ - @DataClass.Generated.Member public @NonNull RemoteTransition setRemoteTransition(@NonNull IRemoteTransition value) { mRemoteTransition = value; com.android.internal.util.AnnotationValidations.validate( @@ -133,8 +120,8 @@ public class RemoteTransition implements Parcelable { /** * The application thread that will be running the remote transition. + * @hide */ - @DataClass.Generated.Member public @NonNull RemoteTransition setAppThread(@NonNull IApplicationThread value) { mAppThread = value; return this; @@ -143,14 +130,12 @@ public class RemoteTransition implements Parcelable { /** * A name for this that can be used for debugging. */ - @DataClass.Generated.Member public @NonNull RemoteTransition setDebugName(@NonNull String value) { mDebugName = value; return this; } @Override - @DataClass.Generated.Member public String toString() { // You can override field toString logic by defining methods like: // String fieldNameToString() { ... } @@ -163,7 +148,6 @@ public class RemoteTransition implements Parcelable { } @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) { ... } @@ -178,12 +162,10 @@ public class RemoteTransition implements Parcelable { } @Override - @DataClass.Generated.Member public int describeContents() { return 0; } /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) - @DataClass.Generated.Member protected RemoteTransition(@NonNull android.os.Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } @@ -198,11 +180,8 @@ public class RemoteTransition implements Parcelable { NonNull.class, null, mRemoteTransition); this.mAppThread = appThread; this.mDebugName = debugName; - - // onConstructed(); // You can define this method to get a callback } - @DataClass.Generated.Member public static final @NonNull Parcelable.Creator<RemoteTransition> CREATOR = new Parcelable.Creator<RemoteTransition>() { @Override @@ -215,17 +194,4 @@ public class RemoteTransition implements Parcelable { return new RemoteTransition(in); } }; - - @DataClass.Generated( - time = 1678926409863L, - codegenVersion = "1.0.23", - sourceFile = "frameworks/base/core/java/android/window/RemoteTransition.java", - inputSignatures = "private @android.annotation.NonNull android.window.IRemoteTransition mRemoteTransition\nprivate @android.annotation.Nullable android.app.IApplicationThread mAppThread\nprivate @android.annotation.Nullable java.lang.String mDebugName\npublic @android.annotation.Nullable android.os.IBinder asBinder()\nclass RemoteTransition extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genAidl=true)") - @Deprecated - private void __metadata() {} - - - //@formatter:on - // End of generated code - } diff --git a/core/java/android/window/SplashScreenView.java b/core/java/android/window/SplashScreenView.java index 31e3a342d21a..af3d7eb87f71 100644 --- a/core/java/android/window/SplashScreenView.java +++ b/core/java/android/window/SplashScreenView.java @@ -56,6 +56,8 @@ import com.android.internal.R; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.policy.DecorView; +import java.io.Closeable; +import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.function.Consumer; @@ -568,6 +570,12 @@ public final class SplashScreenView extends FrameLayout { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); releaseAnimationSurfaceHost(); + if (mIconView instanceof ImageView imageView + && imageView.getDrawable() instanceof Closeable closeableDrawable) { + try { + closeableDrawable.close(); + } catch (IOException ignore) { } + } } @Override diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java index d0f3c3eb9455..6f7660a0fb3d 100644 --- a/core/java/android/window/TaskFragmentOperation.java +++ b/core/java/android/window/TaskFragmentOperation.java @@ -84,8 +84,8 @@ public final class TaskFragmentOperation implements Parcelable { /** * Sets the activity navigation to be isolated, where the activity navigation on the * TaskFragment is separated from the rest activities in the Task. Activities cannot be - * started on an isolated TaskFragment unless the activities are launched from the same - * TaskFragment or explicitly requested to. + * started on an isolated TaskFragment unless explicitly requested to. That said, new launched + * activities should be positioned as a sibling to the TaskFragment with higher z-ordering. */ public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11; @@ -149,6 +149,18 @@ public final class TaskFragmentOperation implements Parcelable { */ public static final int OP_TYPE_SET_DECOR_SURFACE_BOOSTED = 18; + /** + * Sets the TaskFragment to be pinned. + * <p> + * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other + * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the + * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or + * explicitly requested to. Non-embeddable activities are not restricted to. + * <p> + * See {@link #OP_TYPE_REORDER_TO_FRONT} on how to reorder a pinned TaskFragment to the top. + */ + public static final int OP_TYPE_SET_PINNED = 19; + @IntDef(prefix = { "OP_TYPE_" }, value = { OP_TYPE_UNKNOWN, OP_TYPE_CREATE_TASK_FRAGMENT, @@ -170,6 +182,7 @@ public final class TaskFragmentOperation implements Parcelable { OP_TYPE_SET_DIM_ON_TASK, OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH, OP_TYPE_SET_DECOR_SURFACE_BOOSTED, + OP_TYPE_SET_PINNED, }) @Retention(RetentionPolicy.SOURCE) public @interface OperationType {} diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 5c113f865d45..461eab61e393 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -18,6 +18,7 @@ package android.window; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import static android.view.WindowManager.TRANSIT_NONE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -93,6 +94,19 @@ public class TaskFragmentOrganizer extends WindowOrganizer { @TaskFragmentTransitionType public static final int TASK_FRAGMENT_TRANSIT_CHANGE = TRANSIT_CHANGE; + + /** + * The task fragment drag resize transition used by activity embedding. + * + * This value is also used in Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE and must not + * conflict with other predefined transition types. + * + * @hide + */ + @WindowManager.TransitionType + @TaskFragmentTransitionType + public static final int TASK_FRAGMENT_TRANSIT_DRAG_RESIZE = TRANSIT_FIRST_CUSTOM + 17; + /** * Introduced a sub set of {@link WindowManager.TransitionType} for the types that are used for * TaskFragment transition. @@ -106,6 +120,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { TASK_FRAGMENT_TRANSIT_OPEN, TASK_FRAGMENT_TRANSIT_CLOSE, TASK_FRAGMENT_TRANSIT_CHANGE, + TASK_FRAGMENT_TRANSIT_DRAG_RESIZE, }) @Retention(RetentionPolicy.SOURCE) public @interface TaskFragmentTransitionType {} diff --git a/core/java/android/window/TaskFragmentParentInfo.java b/core/java/android/window/TaskFragmentParentInfo.java index a77c23475c60..15554167c702 100644 --- a/core/java/android/window/TaskFragmentParentInfo.java +++ b/core/java/android/window/TaskFragmentParentInfo.java @@ -18,6 +18,8 @@ package android.window; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.annotation.TestApi; import android.app.WindowConfiguration; import android.content.res.Configuration; import android.os.Parcel; @@ -27,10 +29,13 @@ import android.view.SurfaceControl; import java.util.Objects; /** - * The information about the parent Task of a particular TaskFragment + * The information about the parent Task of a particular TaskFragment. + * * @hide */ -public class TaskFragmentParentInfo implements Parcelable { +@SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. +@TestApi +public final class TaskFragmentParentInfo implements Parcelable { @NonNull private final Configuration mConfiguration = new Configuration(); @@ -42,6 +47,7 @@ public class TaskFragmentParentInfo implements Parcelable { @Nullable private final SurfaceControl mDecorSurface; + /** @hide */ public TaskFragmentParentInfo(@NonNull Configuration configuration, int displayId, boolean visible, boolean hasDirectActivity, @Nullable SurfaceControl decorSurface) { mConfiguration.setTo(configuration); @@ -51,6 +57,7 @@ public class TaskFragmentParentInfo implements Parcelable { mDecorSurface = decorSurface; } + /** @hide */ public TaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { mConfiguration.setTo(info.getConfiguration()); mDisplayId = info.mDisplayId; @@ -59,7 +66,11 @@ public class TaskFragmentParentInfo implements Parcelable { mDecorSurface = info.mDecorSurface; } - /** The {@link Configuration} of the parent Task */ + /** + * The {@link Configuration} of the parent Task + * + * @hide + */ @NonNull public Configuration getConfiguration() { return mConfiguration; @@ -68,19 +79,27 @@ public class TaskFragmentParentInfo implements Parcelable { /** * The display ID of the parent Task. {@link android.view.Display#INVALID_DISPLAY} means the * Task is detached from previously associated display. + * + * @hide */ public int getDisplayId() { return mDisplayId; } - /** Whether the parent Task is visible or not */ + /** + * Whether the parent Task is visible or not + * + * @hide + */ public boolean isVisible() { return mVisible; } /** * Whether the parent Task has any direct child activity, which is not embedded in any - * TaskFragment, or not + * TaskFragment, or not. + * + * @hide */ public boolean hasDirectActivity() { return mHasDirectActivity; @@ -93,6 +112,8 @@ public class TaskFragmentParentInfo implements Parcelable { * {@link com.android.server.wm.WindowOrganizerController#configurationsAreEqualForOrganizer( * Configuration, Configuration)} to determine if this {@link TaskFragmentParentInfo} should * be dispatched to the client. + * + * @hide */ public boolean equalsForTaskFragmentOrganizer(@Nullable TaskFragmentParentInfo that) { if (that == null) { @@ -103,6 +124,7 @@ public class TaskFragmentParentInfo implements Parcelable { && mDecorSurface == that.mDecorSurface; } + /** @hide */ @Nullable public SurfaceControl getDecorSurface() { return mDecorSurface; @@ -156,6 +178,7 @@ public class TaskFragmentParentInfo implements Parcelable { return result; } + @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. @Override public void writeToParcel(@NonNull Parcel dest, int flags) { mConfiguration.writeToParcel(dest, flags); @@ -173,6 +196,8 @@ public class TaskFragmentParentInfo implements Parcelable { mDecorSurface = in.readTypedObject(SurfaceControl.CREATOR); } + @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. + @NonNull public static final Creator<TaskFragmentParentInfo> CREATOR = new Creator<TaskFragmentParentInfo>() { @Override @@ -186,6 +211,7 @@ public class TaskFragmentParentInfo implements Parcelable { } }; + @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. @Override public int describeContents() { return 0; diff --git a/core/java/android/window/TaskFragmentTransaction.java b/core/java/android/window/TaskFragmentTransaction.java index 4dada108c4c6..1e5b0971aad6 100644 --- a/core/java/android/window/TaskFragmentTransaction.java +++ b/core/java/android/window/TaskFragmentTransaction.java @@ -24,7 +24,6 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.content.Intent; -import android.content.res.Configuration; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; @@ -190,6 +189,10 @@ public final class TaskFragmentTransaction implements Parcelable { @Nullable private IBinder mActivityToken; + /** @see #setOtherActivityToken(IBinder) */ + @Nullable + private IBinder mOtherActivityToken; + @Nullable private TaskFragmentParentInfo mTaskFragmentParentInfo; @@ -211,6 +214,7 @@ public final class TaskFragmentTransaction implements Parcelable { mActivityToken = in.readStrongBinder(); mTaskFragmentParentInfo = in.readTypedObject(TaskFragmentParentInfo.CREATOR); mSurfaceControl = in.readTypedObject(SurfaceControl.CREATOR); + mOtherActivityToken = in.readStrongBinder(); } @Override @@ -225,6 +229,7 @@ public final class TaskFragmentTransaction implements Parcelable { dest.writeStrongBinder(mActivityToken); dest.writeTypedObject(mTaskFragmentParentInfo, flags); dest.writeTypedObject(mSurfaceControl, flags); + dest.writeStrongBinder(mOtherActivityToken); } /** The change is related to the TaskFragment created with this unique token. */ @@ -248,13 +253,6 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } - // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release. - /** Configuration of the parent Task. */ - @NonNull - public Change setTaskConfiguration(@NonNull Configuration configuration) { - return this; - } - /** * If the {@link #TYPE_TASK_FRAGMENT_ERROR} is from a {@link WindowContainerTransaction} * from the {@link TaskFragmentOrganizer}, it may come with an error callback token to @@ -299,12 +297,26 @@ public final class TaskFragmentTransaction implements Parcelable { return this; } - // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release. + /** + * Token of another activity. + * <p>For {@link #TYPE_ACTIVITY_REPARENTED_TO_TASK}, it is the next activity (behind the + * reparented one) that fills the Task and occludes other activities. It will be the + * actual activity token if the activity belongs to the same process as the organizer. + * Otherwise, it is {@code null}. + * + * @hide + */ + @NonNull + public Change setOtherActivityToken(@NonNull IBinder activityToken) { + mOtherActivityToken = requireNonNull(activityToken); + return this; + } + /** * Sets info of the parent Task of the embedded TaskFragment. * @see TaskFragmentParentInfo * - * @hide pending unhide + * @hide */ @NonNull public Change setTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { @@ -338,12 +350,6 @@ public final class TaskFragmentTransaction implements Parcelable { return mTaskId; } - // TODO(b/241043377): Keep this API to prevent @TestApi changes. Remove in the next release. - @Nullable - public Configuration getTaskConfiguration() { - return mTaskFragmentParentInfo.getConfiguration(); - } - @Nullable public IBinder getErrorCallbackToken() { return mErrorCallbackToken; @@ -365,8 +371,16 @@ public final class TaskFragmentTransaction implements Parcelable { return mActivityToken; } - // TODO(b/241043377): Hide this API to prevent @TestApi changes. Remove in the next release. - /** @hide pending unhide */ + /** @hide */ + @Nullable + public IBinder getOtherActivityToken() { + return mOtherActivityToken; + } + + /** + * Obtains the {@link TaskFragmentParentInfo} for this transaction. + */ + @SuppressLint("UnflaggedApi") // @TestApi to replace legacy usages. @Nullable public TaskFragmentParentInfo getTaskFragmentParentInfo() { return mTaskFragmentParentInfo; diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index 41b6d31661ce..f0144cbf0f4a 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -16,6 +16,7 @@ package android.window; +import android.annotation.IntDef; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -32,6 +33,11 @@ import android.os.SystemClock; import android.view.Surface; import android.view.WindowInsetsController; +import com.android.window.flags.Flags; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Represents a task snapshot. * @hide @@ -68,6 +74,21 @@ public class TaskSnapshot implements Parcelable { private final boolean mHasImeSurface; // Must be one of the named color spaces, otherwise, always use SRGB color space. private final ColorSpace mColorSpace; + private int mInternalReferences; + + /** This snapshot object is being broadcast. */ + public static final int REFERENCE_BROADCAST = 1; + /** This snapshot object is in the cache. */ + public static final int REFERENCE_CACHE = 1 << 1; + /** This snapshot object is being persistent. */ + public static final int REFERENCE_PERSIST = 1 << 2; + @IntDef(flag = true, prefix = { "REFERENCE_" }, value = { + REFERENCE_BROADCAST, + REFERENCE_CACHE, + REFERENCE_PERSIST + }) + @Retention(RetentionPolicy.SOURCE) + @interface ReferenceFlags {} public TaskSnapshot(long id, long captureTime, @NonNull ComponentName topActivityComponent, HardwareBuffer snapshot, @@ -296,7 +317,29 @@ public class TaskSnapshot implements Parcelable { + " mWindowingMode=" + mWindowingMode + " mAppearance=" + mAppearance + " mIsTranslucent=" + mIsTranslucent - + " mHasImeSurface=" + mHasImeSurface; + + " mHasImeSurface=" + mHasImeSurface + + " mInternalReferences=" + mInternalReferences; + } + + /** + * Adds a reference when the object is held somewhere. + * Only used in core. + */ + public synchronized void addReference(@ReferenceFlags int usage) { + mInternalReferences |= usage; + } + + /** + * Removes a reference when the object is not held from somewhere. The snapshot will be closed + * once the reference becomes zero. + * Only used in core. + */ + public synchronized void removeReference(@ReferenceFlags int usage) { + mInternalReferences &= ~usage; + if (Flags.releaseSnapshotAggressively() && mInternalReferences == 0 && mSnapshot != null + && !mSnapshot.isClosed()) { + mSnapshot.close(); + } } public static final @NonNull Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() { 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/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index bcae571201a8..4ffd88093531 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -54,6 +54,8 @@ import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -69,6 +71,7 @@ public final class TransitionInfo implements Parcelable { * Modes are only a sub-set of all the transit-types since they are per-container * @hide */ + @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "TRANSIT_" }, value = { TRANSIT_NONE, TRANSIT_OPEN, @@ -102,11 +105,11 @@ public final class TransitionInfo implements Parcelable { /** The container is the display. */ public static final int FLAG_IS_DISPLAY = 1 << 5; + // TODO(b/194540864): Once we can include all windows in transition, then replace this with + // something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations. /** * Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is * used to prevent seamless rotation. - * TODO(b/194540864): Once we can include all windows in transition, then replace this with - * something like FLAG_IS_SYSTEM_ALERT instead. Then we can do mixed rotations. */ public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7; @@ -173,6 +176,7 @@ public final class TransitionInfo implements Parcelable { public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION; /** @hide */ + @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, FLAG_SHOW_WALLPAPER, @@ -267,11 +271,11 @@ public final class TransitionInfo implements Parcelable { } /** @see #getRoot */ - public void addRoot(Root other) { + public void addRoot(@NonNull Root other) { mRoots.add(other); } - public void setAnimationOptions(AnimationOptions options) { + public void setAnimationOptions(@Nullable AnimationOptions options) { mOptions = options; } @@ -336,6 +340,7 @@ public final class TransitionInfo implements Parcelable { return mRoots.get(0).mLeash; } + @Nullable public AnimationOptions getAnimationOptions() { return mOptions; } @@ -601,7 +606,7 @@ public final class TransitionInfo implements Parcelable { * Updates the callsites of all the surfaces in this transition, which aids in the debugging of * lingering surfaces. */ - public void setUnreleasedWarningCallSiteForAllSurfaces(String callsite) { + public void setUnreleasedWarningCallSiteForAllSurfaces(@Nullable String callsite) { for (int i = mChanges.size() - 1; i >= 0; --i) { mChanges.get(i).getLeash().setUnreleasedWarningCallSite(callsite); } @@ -613,6 +618,7 @@ public final class TransitionInfo implements Parcelable { * the caller's references. Use this only if you need to "send" this to a local function which * assumes it is being called from a remote caller. */ + @NonNull public TransitionInfo localRemoteCopy() { final TransitionInfo out = new TransitionInfo(mType, mFlags); out.mTrack = mTrack; @@ -891,7 +897,7 @@ public final class TransitionInfo implements Parcelable { return mTaskInfo; } - public boolean getAllowEnterPip() { + public boolean isAllowEnterPip() { return mAllowEnterPip; } @@ -1042,6 +1048,7 @@ public final class TransitionInfo implements Parcelable { } /** Represents animation options during a transition */ + @SuppressWarnings("UserHandleName") public static final class AnimationOptions implements Parcelable { private int mType; @@ -1061,7 +1068,7 @@ public final class TransitionInfo implements Parcelable { mType = type; } - public AnimationOptions(Parcel in) { + private AnimationOptions(Parcel in) { mType = in.readInt(); mEnterResId = in.readInt(); mExitResId = in.readInt(); @@ -1076,14 +1083,17 @@ public final class TransitionInfo implements Parcelable { } /** Make basic customized animation for a package */ - public static AnimationOptions makeCommonAnimOptions(String packageName) { + @NonNull + public static AnimationOptions makeCommonAnimOptions(@NonNull String packageName) { AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE); options.mPackageName = packageName; return options; } + /** Make custom animation from the content of LayoutParams */ + @NonNull public static AnimationOptions makeAnimOptionsFromLayoutParameters( - WindowManager.LayoutParams lp) { + @NonNull WindowManager.LayoutParams lp) { AnimationOptions options = new AnimationOptions(ANIM_FROM_STYLE); options.mPackageName = lp.packageName; options.mAnimations = lp.windowAnimations; @@ -1091,7 +1101,7 @@ public final class TransitionInfo implements Parcelable { } /** Add customized window animations */ - public void addOptionsFromLayoutParameters(WindowManager.LayoutParams lp) { + public void addOptionsFromLayoutParameters(@NonNull WindowManager.LayoutParams lp) { mAnimations = lp.windowAnimations; } @@ -1111,8 +1121,11 @@ public final class TransitionInfo implements Parcelable { customTransition.addCustomActivityTransition(enterResId, exitResId, backgroundColor); } - public static AnimationOptions makeCustomAnimOptions(String packageName, int enterResId, - int exitResId, @ColorInt int backgroundColor, boolean overrideTaskTransition) { + /** Make options for a custom animation based on anim resources */ + @NonNull + public static AnimationOptions makeCustomAnimOptions(@NonNull String packageName, + int enterResId, int exitResId, @ColorInt int backgroundColor, + boolean overrideTaskTransition) { AnimationOptions options = new AnimationOptions(ANIM_CUSTOM); options.mPackageName = packageName; options.mEnterResId = enterResId; @@ -1122,6 +1135,8 @@ public final class TransitionInfo implements Parcelable { return options; } + /** Make options for a clip-reveal animation. */ + @NonNull public static AnimationOptions makeClipRevealAnimOptions(int startX, int startY, int width, int height) { AnimationOptions options = new AnimationOptions(ANIM_CLIP_REVEAL); @@ -1129,6 +1144,8 @@ public final class TransitionInfo implements Parcelable { return options; } + /** Make options for a scale-up animation. */ + @NonNull public static AnimationOptions makeScaleUpAnimOptions(int startX, int startY, int width, int height) { AnimationOptions options = new AnimationOptions(ANIM_SCALE_UP); @@ -1136,7 +1153,9 @@ public final class TransitionInfo implements Parcelable { return options; } - public static AnimationOptions makeThumbnailAnimOptions(HardwareBuffer srcThumb, + /** Make options for a thumbnail-scaling animation. */ + @NonNull + public static AnimationOptions makeThumbnailAnimOptions(@NonNull HardwareBuffer srcThumb, int startX, int startY, boolean scaleUp) { AnimationOptions options = new AnimationOptions( scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN); @@ -1145,11 +1164,15 @@ public final class TransitionInfo implements Parcelable { return options; } + /** Make options for an animation that spans activities of different profiles. */ + @NonNull public static AnimationOptions makeCrossProfileAnimOptions() { AnimationOptions options = new AnimationOptions(ANIM_OPEN_CROSS_PROFILE_APPS); return options; } + /** Make options designating this as a scene-transition animation. */ + @NonNull public static AnimationOptions makeSceneTransitionAnimOptions() { AnimationOptions options = new AnimationOptions(ANIM_SCENE_TRANSITION); return options; @@ -1175,14 +1198,17 @@ public final class TransitionInfo implements Parcelable { return mOverrideTaskTransition; } + @Nullable public String getPackageName() { return mPackageName; } + @NonNull public Rect getTransitionBounds() { return mTransitionBounds; } + @Nullable public HardwareBuffer getThumbnail() { return mThumbnail; } @@ -1192,12 +1218,13 @@ public final class TransitionInfo implements Parcelable { } /** Return customized activity transition if existed. */ + @Nullable public CustomActivityTransition getCustomActivityTransition(boolean open) { return open ? mCustomActivityOpenTransition : mCustomActivityCloseTransition; } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mType); dest.writeInt(mEnterResId); dest.writeInt(mExitResId); @@ -1247,6 +1274,7 @@ public final class TransitionInfo implements Parcelable { } @Override + @NonNull public String toString() { final StringBuilder sb = new StringBuilder(32); sb.append("{t=").append(typeToString(mType)); @@ -1261,7 +1289,7 @@ public final class TransitionInfo implements Parcelable { } /** Customized activity transition. */ - public static class CustomActivityTransition implements Parcelable { + public static final class CustomActivityTransition implements Parcelable { private int mCustomEnterResId; private int mCustomExitResId; private int mCustomBackgroundColor; @@ -1302,7 +1330,7 @@ public final class TransitionInfo implements Parcelable { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mCustomEnterResId); dest.writeInt(mCustomExitResId); dest.writeInt(mCustomBackgroundColor); diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index bd54e14bc996..cc225767bd49 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -113,7 +113,7 @@ public final class TransitionRequestInfo implements Parcelable { /** Requested change to a display. */ @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false) - public static class DisplayChange implements Parcelable { + public static final class DisplayChange implements Parcelable { private final int mDisplayId; @Nullable private Rect mStartAbsBounds = null; @Nullable private Rect mEndAbsBounds = null; diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index cd13c4abac0b..4b2beb903325 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -9,6 +9,16 @@ flag { } flag { + name: "disable_thin_letterboxing_policy" + namespace: "large_screen_experiences_app_compat" + description: "Whether reachability is disabled in case of thin letterboxing" + bug: "341027847" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "allows_screen_size_decoupled_from_status_bar_and_cutout" namespace: "large_screen_experiences_app_compat" description: "When necessary, configuration decoupled from status bar and display cutout" diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index e8b4f0bec6a8..0590c407d7e4 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -71,3 +71,24 @@ flag { description: "Enables quick switch for desktop mode" bug: "338066529" } + +flag { + name: "enable_additional_windows_above_status_bar" + namespace: "lse_desktop_experience" + description: "Allows for additional windows tied to WindowDecoration to be layered between status bar and notification shade." + bug: "316186265" +} + +flag { + name: "enable_app_header_with_task_density" + namespace: "lse_desktop_experience" + description: "Matches the App Header density to that of the app window, instead of SysUI's" + bug: "332414819" +} + +flag { + name: "enable_themed_app_headers" + namespace: "lse_desktop_experience" + description: "Makes the App Header style adapt to the system's and app's light/dark theme" + bug: "328668781" +} diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig index 33af48636f02..69cac6f3dfa5 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -49,3 +49,10 @@ flag { description: "Prevent BAL based on it is bound by foreground Uid but the app switch is stopped." bug: "283801068" } + +flag { + name: "bal_improved_metrics" + namespace: "responsible_apis" + description: "Improved metrics." + bug: "339245692" +} diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index ee3e34f2b9e2..f08f5b8fddbe 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -163,4 +163,12 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "release_snapshot_aggressively" + namespace: "windowing_frontend" + description: "Actively release task snapshot memory" + bug: "238206323" + is_fixed_read_only: true }
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 945164a7f25f..ef55cc044c7c 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -120,4 +120,24 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + namespace: "windowing_sdk" + name: "fix_pip_restore_to_overlay" + description: "Restore exit-pip activity back to ActivityEmbedding overlay" + bug: "297887697" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "windowing_sdk" + name: "activity_embedding_animation_customization_flag" + description: "Whether the animation customization feature for AE is enabled" + bug: "293658614" + metadata { + purpose: PURPOSE_FEATURE + } }
\ No newline at end of file diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java index 6864bf7bae16..8e18f842a055 100644 --- a/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java +++ b/core/java/com/android/internal/accessibility/util/AccessibilityStatsLogUtils.java @@ -24,6 +24,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; +import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__DISABLED; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SERVICE_STATUS__ENABLED; @@ -32,6 +33,7 @@ import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON_LONG_PRESS; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__QUICK_SETTINGS; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TWO_FINGER_TRIPLE_TAP; import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; @@ -48,7 +50,6 @@ import android.content.ComponentName; import android.content.Context; import android.provider.Settings; -import com.android.internal.accessibility.common.ShortcutConstants; import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; import com.android.internal.util.FrameworkStatsLog; @@ -248,6 +249,8 @@ public final class AccessibilityStatsLogUtils { } case HARDWARE: return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY; + case QUICK_SETTINGS: + return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__QUICK_SETTINGS; } return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; } diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 84715aa80edb..17adee4cc49e 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -917,7 +917,7 @@ public class ResolverActivity extends Activity implements mSystemWindowInsets = insets.getSystemWindowInsets(); mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, - mSystemWindowInsets.right, 0); + mSystemWindowInsets.right, mSystemWindowInsets.bottom); resetButtonBar(); @@ -946,7 +946,7 @@ public class ResolverActivity extends Activity implements if (mSystemWindowInsets != null) { mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top, - mSystemWindowInsets.right, 0); + mSystemWindowInsets.right, mSystemWindowInsets.bottom); } } diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index 7ac553c56bf7..ad7329485a0e 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -22,6 +22,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; @@ -68,7 +69,8 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { @UnsupportedAppUsage public PackageMonitor() { - this(true); + // If the feature flag is enabled, set mSupportsPackageRestartQuery to false by default + this(!Flags.packageRestartQueryDisabledByDefault()); } /** @@ -164,6 +166,13 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } /** + * Same as {@link #onPackageAdded(String, int)}, but this callback + * has extras passed in. + */ + public void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) { + } + + /** * Called when a package is really removed (and not replaced). */ @UnsupportedAppUsage @@ -171,19 +180,47 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } /** + * Same as {@link #onPackageRemoved(String, int)}, but this callback + * has extras passed in. + */ + public void onPackageRemovedWithExtras(String packageName, int uid, Bundle extras) { + } + + /** * Called when a package is really removed (and not replaced) for * all users on the device. */ public void onPackageRemovedAllUsers(String packageName, int uid) { } + /** + * Same as {@link #onPackageRemovedAllUsers(String, int)}, but this callback + * has extras passed in. + */ + public void onPackageRemovedAllUsersWithExtras(String packageName, int uid, Bundle extras) { + } + public void onPackageUpdateStarted(String packageName, int uid) { } + /** + * Same as {@link #onPackageUpdateStarted(String, int)}, but this callback + * has extras passed in. + */ + public void onPackageUpdateStartedWithExtras(String packageName, int uid, Bundle extras) { + } + public void onPackageUpdateFinished(String packageName, int uid) { } /** + * Same as {@link #onPackageUpdateFinished(String, int)}, but this callback + * has extras passed in. + */ + public void onPackageUpdateFinishedWithExtras(String packageName, int uid, Bundle extras) { + } + + /** * Direct reflection of {@link Intent#ACTION_PACKAGE_CHANGED * Intent.ACTION_PACKAGE_CHANGED} being received, informing you of * changes to the enabled/disabled state of components in a package @@ -281,6 +318,13 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } /** + * Same as {@link #onPackageModified(String)}, but this callback + * has extras passed in. + */ + public void onPackageModifiedWithExtras(@NonNull String packageName, Bundle extras) { + } + + /** * Called when a package in the stopped state is started for some reason. * * @param packageName Name of the package that was unstopped @@ -423,10 +467,13 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { mModifiedPackages = mTempArray; mChangeType = PACKAGE_UPDATING; onPackageUpdateFinished(pkg, uid); + onPackageUpdateFinishedWithExtras(pkg, uid, intent.getExtras()); onPackageModified(pkg); + onPackageModifiedWithExtras(pkg, intent.getExtras()); } else { mChangeType = PACKAGE_PERMANENT_CHANGE; onPackageAdded(pkg, uid); + onPackageAddedWithExtras(pkg, uid, intent.getExtras()); } onPackageAppearedWithExtras(pkg, intent.getExtras()); onPackageAppeared(pkg, mChangeType); @@ -440,11 +487,13 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { mChangeType = PACKAGE_UPDATING; onPackageUpdateStarted(pkg, uid); + onPackageUpdateStartedWithExtras(pkg, uid, intent.getExtras()); if (intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false)) { // In case it is a removal event due to archiving, we trigger package // update event to refresh details like icons, title etc. corresponding to // the archived app. onPackageModified(pkg); + onPackageModifiedWithExtras(pkg, intent.getExtras()); } } else { mChangeType = PACKAGE_PERMANENT_CHANGE; @@ -453,8 +502,10 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { // it when it is re-added. mSomePackagesChanged = true; onPackageRemoved(pkg, uid); + onPackageRemovedWithExtras(pkg, uid, intent.getExtras()); if (intent.getBooleanExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, false)) { onPackageRemovedAllUsers(pkg, uid); + onPackageRemovedAllUsersWithExtras(pkg, uid, intent.getExtras()); } } onPackageDisappearedWithExtras(pkg, intent.getExtras()); @@ -474,6 +525,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } onPackageChangedWithExtras(pkg, intent.getExtras()); onPackageModified(pkg); + onPackageModifiedWithExtras(pkg, intent.getExtras()); } } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { String pkg = getPackageName(intent); diff --git a/core/java/com/android/internal/content/om/OverlayConfigParser.java b/core/java/com/android/internal/content/om/OverlayConfigParser.java index faaf7d5cef18..8132652ed6f4 100644 --- a/core/java/com/android/internal/content/om/OverlayConfigParser.java +++ b/core/java/com/android/internal/content/om/OverlayConfigParser.java @@ -24,6 +24,8 @@ import android.content.pm.PackagePartitions; import android.content.pm.PackagePartitions.SystemPartition; import android.os.Build; import android.os.FileUtils; +import android.os.SystemProperties; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Xml; @@ -241,6 +243,18 @@ public final class OverlayConfigParser { } } + @FunctionalInterface + public interface SysPropWrapper{ + /** + * Get system property + * + * @param property the key to look up. + * + * @return The property value if found, empty string otherwise. + */ + String get(String property); + } + /** * Retrieves overlays configured within the partition in increasing priority order. * @@ -320,6 +334,76 @@ public final class OverlayConfigParser { } /** + * Expand the property inside a rro configuration path. + * + * A RRO configuration can contain a property, this method expands + * the property to its value. + * + * Only read only properties allowed, prefixed with ro. Other + * properties will raise exception. + * + * Only a single property in the path is allowed. + * + * Example "${ro.boot.hardware.sku}/config.xml" would expand to + * "G020N/config.xml" + * + * @param configPath path to expand + * @param sysPropWrapper method used for reading properties + * + * @return The expanded path. Returns null if configPath is null. + */ + @VisibleForTesting + public static String expandProperty(String configPath, + SysPropWrapper sysPropWrapper) { + if (configPath == null) { + return null; + } + + int propStartPos = configPath.indexOf("${"); + if (propStartPos == -1) { + // No properties inside the string, return as is + return configPath; + } + + final StringBuilder sb = new StringBuilder(); + sb.append(configPath.substring(0, propStartPos)); + + // Read out the end position + int propEndPos = configPath.indexOf("}", propStartPos); + if (propEndPos == -1) { + throw new IllegalStateException("Malformed property, unmatched braces, in: " + + configPath); + } + + // Confirm that there is only one property inside the string + if (configPath.indexOf("${", propStartPos + 2) != -1) { + throw new IllegalStateException("Only a single property supported in path: " + + configPath); + } + + final String propertyName = configPath.substring(propStartPos + 2, propEndPos); + if (!propertyName.startsWith("ro.")) { + throw new IllegalStateException("Only read only properties can be used when " + + "merging RRO config files: " + propertyName); + } + final String propertyValue = sysPropWrapper.get(propertyName); + if (TextUtils.isEmpty(propertyValue)) { + throw new IllegalStateException("Property is empty or doesn't exist: " + propertyName); + } + Log.d(TAG, String.format("Using property in overlay config path: \"%s\"", propertyName)); + sb.append(propertyValue); + + // propEndPos points to '}', need to step to next character, might be outside of string + propEndPos = propEndPos + 1; + // Append the remainder, if exists + if (propEndPos < configPath.length()) { + sb.append(configPath.substring(propEndPos)); + } + + return sb.toString(); + } + + /** * Parses a <merge> tag within an overlay configuration file. * * Merge tags allow for other configuration files to be "merged" at the current parsing @@ -331,10 +415,21 @@ public final class OverlayConfigParser { @Nullable OverlayScanner scanner, @Nullable Map<String, ParsedOverlayInfo> packageManagerOverlayInfos, @NonNull ParsingContext parsingContext) { - final String path = parser.getAttributeValue(null, "path"); + final String path; + + try { + SysPropWrapper sysPropWrapper = p -> { + return SystemProperties.get(p, ""); + }; + path = expandProperty(parser.getAttributeValue(null, "path"), sysPropWrapper); + } catch (IllegalStateException e) { + throw new IllegalStateException(String.format("<merge> path expand error in %s at %s", + configFile, parser.getPositionDescription()), e); + } + if (path == null) { - throw new IllegalStateException(String.format("<merge> without path in %s at %s" - + configFile, parser.getPositionDescription())); + throw new IllegalStateException(String.format("<merge> without path in %s at %s", + configFile, parser.getPositionDescription())); } if (path.startsWith("/")) { diff --git a/core/java/com/android/internal/inputmethod/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl index ab4edb65780b..ebae39ee3241 100644 --- a/core/java/com/android/internal/inputmethod/IImeTracker.aidl +++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl @@ -64,20 +64,28 @@ interface IImeTracker { oneway void onCancelled(in ImeTracker.Token statsToken, int phase); /** - * Called when the IME show request is successful. + * Called when the show IME request is successful. * * @param statsToken the token tracking the current IME request. */ oneway void onShown(in ImeTracker.Token statsToken); /** - * Called when the IME hide request is successful. + * Called when the hide IME request is successful. * * @param statsToken the token tracking the current IME request. */ oneway void onHidden(in ImeTracker.Token statsToken); /** + * Called when the user-controlled IME request was dispatched to the requesting app. The + * user animation can take an undetermined amount of time, so it shouldn't be tracked. + * + * @param statsToken the token tracking the current IME request. + */ + oneway void onDispatched(in ImeTracker.Token statsToken); + + /** * Checks whether there are any pending IME visibility requests. * * @return {@code true} iff there are pending IME visibility requests. diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java index ee9c3aa57d72..cd4ccdad6f58 100644 --- a/core/java/com/android/internal/inputmethod/ImeTracing.java +++ b/core/java/com/android/internal/inputmethod/ImeTracing.java @@ -60,7 +60,9 @@ public abstract class ImeTracing { */ public static ImeTracing getInstance() { if (sInstance == null) { - if (isSystemProcess()) { + if (android.tracing.Flags.perfettoIme()) { + sInstance = new ImeTracingPerfettoImpl(); + } else if (isSystemProcess()) { sInstance = new ImeTracingServerImpl(); } else { sInstance = new ImeTracingClientImpl(); @@ -78,7 +80,7 @@ public abstract class ImeTracing { * and {@see #IME_TRACING_FROM_IMS} * @param where */ - public void sendToService(byte[] protoDump, int source, String where) { + protected void sendToService(byte[] protoDump, int source, String where) { InputMethodManagerGlobal.startProtoDump(protoDump, source, where, e -> Log.e(TAG, "Exception while sending ime-related dump to server", e)); } diff --git a/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java new file mode 100644 index 000000000000..24cd1c9cd7de --- /dev/null +++ b/core/java/com/android/internal/inputmethod/ImeTracingPerfettoImpl.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import static android.tracing.perfetto.DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.internal.perfetto.protos.Inputmethodeditor.InputMethodClientsTraceProto; +import android.internal.perfetto.protos.Inputmethodeditor.InputMethodManagerServiceTraceProto; +import android.internal.perfetto.protos.Inputmethodeditor.InputMethodServiceTraceProto; +import android.internal.perfetto.protos.TracePacketOuterClass.TracePacket; +import android.internal.perfetto.protos.WinscopeExtensionsImplOuterClass.WinscopeExtensionsImpl; +import android.os.SystemClock; +import android.os.Trace; +import android.tracing.inputmethod.InputMethodDataSource; +import android.tracing.perfetto.DataSourceParams; +import android.tracing.perfetto.InitArguments; +import android.tracing.perfetto.Producer; +import android.util.proto.ProtoOutputStream; +import android.view.inputmethod.InputMethodManager; + +import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * An implementation of {@link ImeTracing} for perfetto tracing. + */ +final class ImeTracingPerfettoImpl extends ImeTracing { + private final AtomicInteger mTracingSessionsCount = new AtomicInteger(0); + private final AtomicBoolean mIsClientDumpInProgress = new AtomicBoolean(false); + private final AtomicBoolean mIsServiceDumpInProgress = new AtomicBoolean(false); + private final AtomicBoolean mIsManagerServiceDumpInProgress = new AtomicBoolean(false); + private final InputMethodDataSource mDataSource = new InputMethodDataSource( + mTracingSessionsCount::incrementAndGet, + mTracingSessionsCount::decrementAndGet); + + ImeTracingPerfettoImpl() { + Producer.init(InitArguments.DEFAULTS); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .setNoFlush(true) + .setWillNotifyOnStop(false) + .build(); + mDataSource.register(params); + } + + + @Override + public void triggerClientDump(String where, InputMethodManager immInstance, + @Nullable byte[] icProto) { + if (!isEnabled() || !isAvailable()) { + return; + } + + if (!mIsClientDumpInProgress.compareAndSet(false, true)) { + return; + } + + if (immInstance == null) { + return; + } + + try { + Trace.beginSection("inputmethod_client_dump"); + mDataSource.trace((ctx) -> { + final ProtoOutputStream os = ctx.newTracePacket(); + os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos()); + final long tokenWinscopeExtensions = + os.start(TracePacket.WINSCOPE_EXTENSIONS); + final long tokenExtensionsField = + os.start(WinscopeExtensionsImpl.INPUTMETHOD_CLIENTS); + os.write(InputMethodClientsTraceProto.WHERE, where); + final long tokenClient = + os.start(InputMethodClientsTraceProto.CLIENT); + immInstance.dumpDebug(os, icProto); + os.end(tokenClient); + os.end(tokenExtensionsField); + os.end(tokenWinscopeExtensions); + }); + } finally { + mIsClientDumpInProgress.set(false); + Trace.endSection(); + } + } + + @Override + public void triggerServiceDump(String where, + @NonNull ServiceDumper dumper, @Nullable byte[] icProto) { + if (!isEnabled() || !isAvailable()) { + return; + } + + if (!mIsServiceDumpInProgress.compareAndSet(false, true)) { + return; + } + + try { + Trace.beginSection("inputmethod_service_dump"); + mDataSource.trace((ctx) -> { + final ProtoOutputStream os = ctx.newTracePacket(); + os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos()); + final long tokenWinscopeExtensions = + os.start(TracePacket.WINSCOPE_EXTENSIONS); + final long tokenExtensionsField = + os.start(WinscopeExtensionsImpl.INPUTMETHOD_SERVICE); + os.write(InputMethodServiceTraceProto.WHERE, where); + dumper.dumpToProto(os, icProto); + os.end(tokenExtensionsField); + os.end(tokenWinscopeExtensions); + }); + } finally { + mIsServiceDumpInProgress.set(false); + Trace.endSection(); + } + } + + @Override + public void triggerManagerServiceDump(@NonNull String where, @NonNull ServiceDumper dumper) { + if (!isEnabled() || !isAvailable()) { + return; + } + + if (!mIsManagerServiceDumpInProgress.compareAndSet(false, true)) { + return; + } + + try { + Trace.beginSection("inputmethod_manager_service_dump"); + mDataSource.trace((ctx) -> { + final ProtoOutputStream os = ctx.newTracePacket(); + os.write(TracePacket.TIMESTAMP, SystemClock.elapsedRealtimeNanos()); + final long tokenWinscopeExtensions = + os.start(TracePacket.WINSCOPE_EXTENSIONS); + final long tokenExtensionsField = + os.start(WinscopeExtensionsImpl.INPUTMETHOD_MANAGER_SERVICE); + os.write(InputMethodManagerServiceTraceProto.WHERE, where); + dumper.dumpToProto(os, null); + os.end(tokenExtensionsField); + os.end(tokenWinscopeExtensions); + }); + } finally { + mIsManagerServiceDumpInProgress.set(false); + Trace.endSection(); + } + } + + @Override + public boolean isEnabled() { + return mTracingSessionsCount.get() > 0; + } + + @Override + public void startTrace(@Nullable PrintWriter pw) { + // Intentionally left empty. Tracing start/stop is managed through Perfetto. + } + + @Override + public void stopTrace(@Nullable PrintWriter pw) { + // Intentionally left empty. Tracing start/stop is managed through Perfetto. + } + + @Override + public void addToBuffer(ProtoOutputStream proto, int source) { + // Intentionally left empty. Only used for legacy tracing. + } +} diff --git a/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java b/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java new file mode 100644 index 000000000000..92d453ba0e9f --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InlineSuggestionsRequestCallback.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.inputmethod; + +import android.annotation.BinderThread; +import android.view.autofill.AutofillId; +import android.view.inputmethod.InlineSuggestionsRequest; + +/** + * An internal interface that mirrors {@link IInlineSuggestionsRequestCallback}. + * + * <p>This interface is used to forward incoming IPCs from + * {@link com.android.server.inputmethod.AutofillSuggestionsController} to + * {@link com.android.server.autofill.AutofillInlineSuggestionsRequestSession}.</p> + */ +public interface InlineSuggestionsRequestCallback { + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsUnsupported()}. + */ + @BinderThread + void onInlineSuggestionsUnsupported(); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsRequest( + * InlineSuggestionsRequest, IInlineSuggestionsResponseCallback)}. + */ + @BinderThread + void onInlineSuggestionsRequest(InlineSuggestionsRequest request, + IInlineSuggestionsResponseCallback callback); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodStartInput(AutofillId)}. + */ + @BinderThread + void onInputMethodStartInput(AutofillId imeFieldId); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodShowInputRequested(boolean)}. + */ + @BinderThread + void onInputMethodShowInputRequested(boolean requestResult); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodStartInputView()}. + */ + @BinderThread + void onInputMethodStartInputView(); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodFinishInputView()}. + */ + @BinderThread + void onInputMethodFinishInputView(); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInputMethodFinishInput()}. + */ + @BinderThread + void onInputMethodFinishInput(); + + /** + * Forwards {@link IInlineSuggestionsRequestCallback#onInlineSuggestionsSessionInvalidated()}. + */ + @BinderThread + void onInlineSuggestionsSessionInvalidated(); +} diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index a0aad31d2e04..2a5593f6d584 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -297,6 +297,8 @@ public final class InputMethodDebug { return "SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT"; case SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION: return "SHOW_SOFT_INPUT_IMM_DEPRECATION"; + case SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION: + return "CONTROL_WINDOW_INSETS_ANIMATION"; default: return "Unknown=" + reason; } diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java index da738a01ec39..eb6a81031321 100644 --- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java +++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java @@ -88,6 +88,7 @@ import java.lang.annotation.Retention; SoftInputShowHideReason.HIDE_SOFT_INPUT_REQUEST_HIDE_WITH_CONTROL, SoftInputShowHideReason.SHOW_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT, SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION, + SoftInputShowHideReason.CONTROL_WINDOW_INSETS_ANIMATION, }) public @interface SoftInputShowHideReason { /** Default, undefined reason. */ @@ -397,4 +398,10 @@ public @interface SoftInputShowHideReason { * {@link InputMethodManager#showSoftInputFromInputMethod(IBinder, int)}. */ int SHOW_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_IMM_DEPRECATION; + + /** + * Show / Hide soft input by application-controlled animation in + * {@link android.view.InsetsController#controlWindowInsetsAnimation}. + */ + int CONTROL_WINDOW_INSETS_ANIMATION = ImeProtoEnums.REASON_CONTROL_WINDOW_INSETS_ANIMATION; } diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java index f2d2c1b637c6..6ffa8261c8fd 100644 --- a/core/java/com/android/internal/jank/Cuj.java +++ b/core/java/com/android/internal/jank/Cuj.java @@ -134,10 +134,12 @@ public class Cuj { public static final int CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK = 99; public static final int CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK = 100; public static final int CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK = 101; + public static final int CUJ_LAUNCHER_PRIVATE_SPACE_LOCK = 102; + public static final int CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK = 103; // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. @VisibleForTesting - static final int LAST_CUJ = CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK; + static final int LAST_CUJ = CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK; /** @hide */ @IntDef({ @@ -230,7 +232,9 @@ public class Cuj { CUJ_LAUNCHER_TASKBAR_ALL_APPS_SEARCH_BACK, CUJ_LAUNCHER_WIDGET_PICKER_CLOSE_BACK, CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK, - CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK + CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK, + CUJ_LAUNCHER_PRIVATE_SPACE_LOCK, + CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -335,6 +339,8 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_PICKER_SEARCH_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_PICKER_SEARCH_BACK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_LOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_LOCK; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_PRIVATE_SPACE_UNLOCK; } private Cuj() { @@ -533,6 +539,10 @@ public class Cuj { return "LAUNCHER_WIDGET_BOTTOM_SHEET_CLOSE_BACK"; case CUJ_LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK: return "LAUNCHER_WIDGET_EDU_SHEET_CLOSE_BACK"; + case CUJ_LAUNCHER_PRIVATE_SPACE_LOCK: + return "LAUNCHER_PRIVATE_SPACE_LOCK"; + case CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK: + return "LAUNCHER_PRIVATE_SPACE_UNLOCK"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java index 9f9aae53e0af..24971f51aabf 100644 --- a/core/java/com/android/internal/os/PowerStats.java +++ b/core/java/com/android/internal/os/PowerStats.java @@ -19,6 +19,7 @@ package com.android.internal.os; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.BatteryConsumer; +import android.os.BatteryStats; import android.os.Bundle; import android.os.Parcel; import android.os.PersistableBundle; @@ -34,8 +35,12 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for @@ -75,6 +80,10 @@ public final class PowerStats { */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public static class Descriptor { + public static final String EXTRA_DEVICE_STATS_FORMAT = "format-device"; + public static final String EXTRA_STATE_STATS_FORMAT = "format-state"; + public static final String EXTRA_UID_STATS_FORMAT = "format-uid"; + public static final String XML_TAG_DESCRIPTOR = "descriptor"; private static final String XML_ATTR_ID = "id"; private static final String XML_ATTR_NAME = "name"; @@ -120,6 +129,10 @@ public final class PowerStats { */ public final PersistableBundle extras; + private PowerStatsFormatter mDeviceStatsFormatter; + private PowerStatsFormatter mStateStatsFormatter; + private PowerStatsFormatter mUidStatsFormatter; + public Descriptor(@BatteryConsumer.PowerComponent int powerComponentId, int statsArrayLength, @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength, int uidStatsArrayLength, @@ -131,7 +144,7 @@ public final class PowerStats { public Descriptor(int customPowerComponentId, String name, int statsArrayLength, @Nullable SparseArray<String> stateLabels, int stateStatsArrayLength, - int uidStatsArrayLength, PersistableBundle extras) { + int uidStatsArrayLength, @NonNull PersistableBundle extras) { if (statsArrayLength > MAX_STATS_ARRAY_LENGTH) { throw new IllegalArgumentException( "statsArrayLength is too high. Max = " + MAX_STATS_ARRAY_LENGTH); @@ -154,6 +167,39 @@ public final class PowerStats { } /** + * Returns a custom formatter for this type of power stats. + */ + public PowerStatsFormatter getDeviceStatsFormatter() { + if (mDeviceStatsFormatter == null) { + mDeviceStatsFormatter = new PowerStatsFormatter( + extras.getString(EXTRA_DEVICE_STATS_FORMAT)); + } + return mDeviceStatsFormatter; + } + + /** + * Returns a custom formatter for this type of power stats, specifically per-state stats. + */ + public PowerStatsFormatter getStateStatsFormatter() { + if (mStateStatsFormatter == null) { + mStateStatsFormatter = new PowerStatsFormatter( + extras.getString(EXTRA_STATE_STATS_FORMAT)); + } + return mStateStatsFormatter; + } + + /** + * Returns a custom formatter for this type of power stats, specifically per-UID stats. + */ + public PowerStatsFormatter getUidStatsFormatter() { + if (mUidStatsFormatter == null) { + mUidStatsFormatter = new PowerStatsFormatter( + extras.getString(EXTRA_UID_STATS_FORMAT)); + } + return mUidStatsFormatter; + } + + /** * Returns the label associated with the give state key, e.g. "5G-high" for the * state of Mobile Radio representing the 5G mode and high signal power. */ @@ -491,20 +537,22 @@ public final class PowerStats { StringBuilder sb = new StringBuilder(); sb.append("duration=").append(durationMs).append(" ").append(descriptor.name); if (stats.length > 0) { - sb.append("=").append(Arrays.toString(stats)); + sb.append("=").append(descriptor.getDeviceStatsFormatter().format(stats)); } if (descriptor.stateStatsArrayLength != 0) { + PowerStatsFormatter formatter = descriptor.getStateStatsFormatter(); for (int i = 0; i < stateStats.size(); i++) { - sb.append(" ["); + sb.append(" ("); sb.append(descriptor.getStateLabel(stateStats.keyAt(i))); - sb.append("]="); - sb.append(Arrays.toString(stateStats.valueAt(i))); + sb.append(") "); + sb.append(formatter.format(stateStats.valueAt(i))); } } + PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter(); for (int i = 0; i < uidStats.size(); i++) { sb.append(uidPrefix) .append(UserHandle.formatUid(uidStats.keyAt(i))) - .append(": ").append(Arrays.toString(uidStats.valueAt(i))); + .append(": ").append(uidStatsFormatter.format(uidStats.valueAt(i))); } return sb.toString(); } @@ -513,26 +561,29 @@ public final class PowerStats { * Prints the contents of the stats snapshot. */ public void dump(IndentingPrintWriter pw) { - pw.println("PowerStats: " + descriptor.name + " (" + descriptor.powerComponentId + ')'); + pw.println(descriptor.name + " (" + descriptor.powerComponentId + ')'); pw.increaseIndent(); pw.print("duration", durationMs).println(); + if (descriptor.statsArrayLength != 0) { - pw.print("stats", Arrays.toString(stats)).println(); + pw.println(descriptor.getDeviceStatsFormatter().format(stats)); } if (descriptor.stateStatsArrayLength != 0) { + PowerStatsFormatter formatter = descriptor.getStateStatsFormatter(); for (int i = 0; i < stateStats.size(); i++) { - pw.print("state "); + pw.print(" ("); pw.print(descriptor.getStateLabel(stateStats.keyAt(i))); - pw.print(": "); - pw.print(Arrays.toString(stateStats.valueAt(i))); + pw.print(") "); + pw.print(formatter.format(stateStats.valueAt(i))); pw.println(); } } + PowerStatsFormatter uidStatsFormatter = descriptor.getUidStatsFormatter(); for (int i = 0; i < uidStats.size(); i++) { pw.print("UID "); - pw.print(uidStats.keyAt(i)); + pw.print(UserHandle.formatUid(uidStats.keyAt(i))); pw.print(": "); - pw.print(Arrays.toString(uidStats.valueAt(i))); + pw.print(uidStatsFormatter.format(uidStats.valueAt(i))); pw.println(); } pw.decreaseIndent(); @@ -542,4 +593,126 @@ public final class PowerStats { public String toString() { return "PowerStats: " + formatForBatteryHistory(" UID "); } + + public static class PowerStatsFormatter { + private static class Section { + public String label; + public int position; + public int length; + public boolean optional; + public boolean typePower; + } + + private static final double NANO_TO_MILLI_MULTIPLIER = 1.0 / 1000000.0; + private static final Pattern SECTION_PATTERN = + Pattern.compile("([^:]+):(\\d+)(\\[(?<L>\\d+)])?(?<F>\\S*)\\s*"); + private final List<Section> mSections; + + public PowerStatsFormatter(String format) { + mSections = parseFormat(format); + } + + /** + * Produces a formatted string representing the supplied array, with labels + * and other adornments specific to the power stats layout. + */ + public String format(long[] stats) { + return format(mSections, stats); + } + + private List<Section> parseFormat(String format) { + if (format == null || format.isBlank()) { + return null; + } + + ArrayList<Section> sections = new ArrayList<>(); + Matcher matcher = SECTION_PATTERN.matcher(format); + for (int position = 0; position < format.length(); position = matcher.end()) { + if (!matcher.find() || matcher.start() != position) { + Slog.wtf(TAG, "Bad power stats format '" + format + "'"); + return null; + } + Section section = new Section(); + section.label = matcher.group(1); + section.position = Integer.parseUnsignedInt(matcher.group(2)); + String length = matcher.group("L"); + if (length != null) { + section.length = Integer.parseUnsignedInt(length); + } else { + section.length = 1; + } + String flags = matcher.group("F"); + if (flags != null) { + for (int i = 0; i < flags.length(); i++) { + char flag = flags.charAt(i); + switch (flag) { + case '?': + section.optional = true; + break; + case 'p': + section.typePower = true; + break; + default: + Slog.e(TAG, + "Unsupported format option '" + flag + "' in " + format); + break; + } + } + } + sections.add(section); + } + + return sections; + } + + private String format(List<Section> sections, long[] stats) { + if (sections == null) { + return Arrays.toString(stats); + } + + StringBuilder sb = new StringBuilder(); + for (int i = 0, count = sections.size(); i < count; i++) { + Section section = sections.get(i); + if (section.length == 0) { + continue; + } + + if (section.optional) { + boolean nonZero = false; + for (int offset = 0; offset < section.length; offset++) { + if (stats[section.position + offset] != 0) { + nonZero = true; + break; + } + } + if (!nonZero) { + continue; + } + } + + if (!sb.isEmpty()) { + sb.append(' '); + } + sb.append(section.label).append(": "); + if (section.length != 1) { + sb.append('['); + } + for (int offset = 0; offset < section.length; offset++) { + if (offset != 0) { + sb.append(", "); + } + if (section.typePower) { + sb.append(BatteryStats.formatCharge( + stats[section.position + offset] * NANO_TO_MILLI_MULTIPLIER)); + } else { + sb.append(stats[section.position + offset]); + } + } + if (section.length != 1) { + sb.append(']'); + } + } + return sb.toString(); + } + } } diff --git a/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java new file mode 100644 index 000000000000..f306b0b02677 --- /dev/null +++ b/core/java/com/android/internal/pm/pkg/component/AconfigFlags.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.pm.pkg.component; + +import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE; + +import android.aconfig.nano.Aconfig; +import android.aconfig.nano.Aconfig.parsed_flag; +import android.aconfig.nano.Aconfig.parsed_flags; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Flags; +import android.content.res.XmlResourceParser; +import android.os.Environment; +import android.os.Process; +import android.util.ArrayMap; +import android.util.Slog; +import android.util.Xml; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.TypedXmlPullParser; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * A class that manages a cache of all device feature flags and their default + override values. + * This class performs a very similar job to the one in {@code SettingsProvider}, with an important + * difference: this is a part of system server and is available for the server startup. Package + * parsing happens at the startup when {@code SettingsProvider} isn't available yet, so we need an + * own copy of the code here. + * @hide + */ +public class AconfigFlags { + private static final String LOG_TAG = "AconfigFlags"; + + private static final List<String> sTextProtoFilesOnDevice = List.of( + "/system/etc/aconfig_flags.pb", + "/system_ext/etc/aconfig_flags.pb", + "/product/etc/aconfig_flags.pb", + "/vendor/etc/aconfig_flags.pb"); + + private final ArrayMap<String, Boolean> mFlagValues = new ArrayMap<>(); + + public AconfigFlags() { + if (!Flags.manifestFlagging()) { + Slog.v(LOG_TAG, "Feature disabled, skipped all loading"); + return; + } + for (String fileName : sTextProtoFilesOnDevice) { + try (var inputStream = new FileInputStream(fileName)) { + loadAconfigDefaultValues(inputStream.readAllBytes()); + } catch (IOException e) { + Slog.e(LOG_TAG, "Failed to read Aconfig values from " + fileName, e); + } + } + if (Process.myUid() == Process.SYSTEM_UID) { + // Server overrides are only accessible to the system, no need to even try loading them + // in user processes. + loadServerOverrides(); + } + } + + private void loadServerOverrides() { + // Reading the proto files is enough for READ_ONLY flags but if it's a READ_WRITE flag + // (which you can check with `flag.getPermission() == flag_permission.READ_WRITE`) then we + // also need to check if there is a value pushed from the server in the file + // `/data/system/users/0/settings_config.xml`. It will be in a <setting> node under the + // root <settings> node with "name" attribute == "flag_namespace/flag_package.flag_name". + // The "value" attribute will be true or false. + // + // The "name" attribute could also be "<namespace>/flag_namespace?flag_package.flag_name" + // (prefixed with "staged/" or "device_config_overrides/" and a different separator between + // namespace and name). This happens when a flag value is overridden either with a pushed + // one from the server, or from the local command. + // When the device reboots during package parsing, the staged value will still be there and + // only later it will become a regular/non-staged value after SettingsProvider is + // initialized. + // + // In all cases, when there is more than one value, the priority is: + // device_config_overrides > staged > default + // + + final var settingsFile = new File(Environment.getUserSystemDirectory(0), + "settings_config.xml"); + try (var inputStream = new FileInputStream(settingsFile)) { + TypedXmlPullParser parser = Xml.resolvePullParser(inputStream); + if (parser.next() != XmlPullParser.END_TAG && "settings".equals(parser.getName())) { + final var flagPriority = new ArrayMap<String, Integer>(); + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + if (!"setting".equals(parser.getName())) { + continue; + } + String name = parser.getAttributeValue(null, "name"); + final String value = parser.getAttributeValue(null, "value"); + if (name == null || value == null) { + continue; + } + // A non-boolean setting is definitely not an Aconfig flag value. + if (!"false".equalsIgnoreCase(value) && !"true".equalsIgnoreCase(value)) { + continue; + } + final var overridePrefix = "device_config_overrides/"; + final var stagedPrefix = "staged/"; + String separator = "/"; + String prefix = "default"; + int priority = 0; + if (name.startsWith(overridePrefix)) { + prefix = overridePrefix; + name = name.substring(overridePrefix.length()); + separator = ":"; + priority = 20; + } else if (name.startsWith(stagedPrefix)) { + prefix = stagedPrefix; + name = name.substring(stagedPrefix.length()); + separator = "*"; + priority = 10; + } + final String flagPackageAndName = parseFlagPackageAndName(name, separator); + if (flagPackageAndName == null) { + continue; + } + // We ignore all settings that aren't for flags. We'll know they are for flags + // if they correspond to flags read from the proto files. + if (!mFlagValues.containsKey(flagPackageAndName)) { + continue; + } + Slog.d(LOG_TAG, "Found " + prefix + + " Aconfig flag value for " + flagPackageAndName + " = " + value); + final Integer currentPriority = flagPriority.get(flagPackageAndName); + if (currentPriority != null && currentPriority >= priority) { + Slog.i(LOG_TAG, "Skipping " + prefix + " flag " + flagPackageAndName + + " because of the existing one with priority " + currentPriority); + continue; + } + flagPriority.put(flagPackageAndName, priority); + mFlagValues.put(flagPackageAndName, Boolean.parseBoolean(value)); + } + } + } catch (IOException | XmlPullParserException e) { + Slog.e(LOG_TAG, "Failed to read Aconfig values from settings_config.xml", e); + } + } + + private static String parseFlagPackageAndName(String fullName, String separator) { + int index = fullName.indexOf(separator); + if (index < 0) { + return null; + } + return fullName.substring(index + 1); + } + + private void loadAconfigDefaultValues(byte[] fileContents) throws IOException { + parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents); + for (parsed_flag flag : parsedFlags.parsedFlag) { + String flagPackageAndName = flag.package_ + "." + flag.name; + boolean flagValue = (flag.state == Aconfig.ENABLED); + Slog.v(LOG_TAG, "Read Aconfig default flag value " + + flagPackageAndName + " = " + flagValue); + mFlagValues.put(flagPackageAndName, flagValue); + } + } + + /** + * Get the flag value, or null if the flag doesn't exist. + * @param flagPackageAndName Full flag name formatted as 'package.flag' + * @return the current value of the given Aconfig flag, or null if there is no such flag + */ + @Nullable + public Boolean getFlagValue(@NonNull String flagPackageAndName) { + Boolean value = mFlagValues.get(flagPackageAndName); + Slog.d(LOG_TAG, "Aconfig flag value for " + flagPackageAndName + " = " + value); + return value; + } + + /** + * Check if the element in {@code parser} should be skipped because of the feature flag. + * @param parser XML parser object currently parsing an element + * @return true if the element is disabled because of its feature flag + */ + public boolean skipCurrentElement(@NonNull XmlResourceParser parser) { + if (!Flags.manifestFlagging()) { + return false; + } + String featureFlag = parser.getAttributeValue(ANDROID_RES_NAMESPACE, "featureFlag"); + if (featureFlag == null) { + return false; + } + featureFlag = featureFlag.strip(); + boolean negated = false; + if (featureFlag.startsWith("!")) { + negated = true; + featureFlag = featureFlag.substring(1).strip(); + } + final Boolean flagValue = getFlagValue(featureFlag); + if (flagValue == null) { + Slog.w(LOG_TAG, "Skipping element " + parser.getName() + + " due to unknown feature flag " + featureFlag); + return true; + } + // Skip if flag==false && attr=="flag" OR flag==true && attr=="!flag" (negated) + if (flagValue == negated) { + Slog.v(LOG_TAG, "Skipping element " + parser.getName() + + " behind feature flag " + featureFlag + " = " + flagValue); + return true; + } + return false; + } + + /** + * Add Aconfig flag values for testing flagging of manifest entries. + * @param flagValues A map of flag name -> value. + */ + @VisibleForTesting + public void addFlagValuesForTesting(@NonNull Map<String, Boolean> flagValues) { + mFlagValues.putAll(flagValues); + } +} diff --git a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java index db08005c833e..8858f9492890 100644 --- a/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java @@ -61,6 +61,9 @@ public class ComponentParseUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + continue; + } final ParseResult result; if ("meta-data".equals(parser.getName())) { diff --git a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java index 0b045919fb13..bb015812c225 100644 --- a/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java +++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java @@ -27,6 +27,7 @@ import android.util.ArraySet; import com.android.internal.R; import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -80,6 +81,9 @@ public class InstallConstraintsTagParser { } return input.success(prefixes); } else if (type == XmlPullParser.START_TAG) { + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + continue; + } if (parser.getName().equals(TAG_FINGERPRINT_PREFIX)) { ParseResult<String> parsedPrefix = readFingerprintPrefixValue(input, res, parser); diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java index 9f71d88c24bc..55baa532b434 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java @@ -393,6 +393,9 @@ public class ParsedActivityUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + continue; + } final ParseResult result; if (parser.getName().equals("intent-filter")) { diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java index 05728eee174f..da48b23a2b81 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java @@ -99,6 +99,9 @@ public class ParsedIntentInfoUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + continue; + } final ParseResult result; String nodeName = parser.getName(); @@ -197,6 +200,9 @@ public class ParsedIntentInfoUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + continue; + } final ParseResult result; String nodeName = parser.getName(); diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java index 12aff1c6669f..6af2a29822a2 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java @@ -36,6 +36,7 @@ import android.util.Slog; import com.android.internal.R; import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; @@ -173,6 +174,9 @@ public class ParsedProviderUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + continue; + } String name = parser.getName(); final ParseResult result; diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java index 4ac542f84226..c68ea2dc8516 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java @@ -34,6 +34,7 @@ import android.os.Build; import com.android.internal.R; import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.pm.pkg.parsing.ParsingUtils; import org.xmlpull.v1.XmlPullParser; @@ -137,6 +138,9 @@ public class ParsedServiceUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (ParsingPackageUtils.getAconfigFlags().skipCurrentElement(parser)) { + continue; + } final ParseResult parseResult; switch (parser.getName()) { diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java index 97ce96ec30f6..44fedb11b043 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java @@ -90,6 +90,7 @@ import com.android.internal.R; import com.android.internal.os.ClassLoaderFactory; import com.android.internal.pm.parsing.pkg.ParsedPackage; import com.android.internal.pm.permission.CompatibilityPermissionInfo; +import com.android.internal.pm.pkg.component.AconfigFlags; import com.android.internal.pm.pkg.component.ComponentMutateUtils; import com.android.internal.pm.pkg.component.ComponentParseUtils; import com.android.internal.pm.pkg.component.InstallConstraintsTagParser; @@ -292,6 +293,7 @@ public class ParsingPackageUtils { @NonNull private final List<PermissionManager.SplitPermissionInfo> mSplitPermissionInfos; private final Callback mCallback; + private static final AconfigFlags sAconfigFlags = new AconfigFlags(); public ParsingPackageUtils(String[] separateProcesses, DisplayMetrics displayMetrics, @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions, @@ -761,6 +763,9 @@ public class ParsingPackageUtils { if (outerDepth + 1 < parser.getDepth() || type != XmlPullParser.START_TAG) { continue; } + if (sAconfigFlags.skipCurrentElement(parser)) { + continue; + } final ParseResult result; String tagName = parser.getName(); @@ -837,6 +842,9 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (sAconfigFlags.skipCurrentElement(parser)) { + continue; + } ParsedMainComponent mainComponent = null; @@ -980,6 +988,9 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (sAconfigFlags.skipCurrentElement(parser)) { + continue; + } String tagName = parser.getName(); final ParseResult result; @@ -1599,6 +1610,9 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (sAconfigFlags.skipCurrentElement(parser)) { + continue; + } final String innerTagName = parser.getName(); if (innerTagName.equals("uses-feature")) { @@ -1839,6 +1853,9 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (sAconfigFlags.skipCurrentElement(parser)) { + continue; + } if (parser.getName().equals("intent")) { ParseResult<ParsedIntentInfoImpl> result = ParsedIntentInfoUtils.parseIntentInfo( null /*className*/, pkg, res, parser, true /*allowGlobs*/, @@ -1908,12 +1925,16 @@ public class ParsingPackageUtils { } else if (parser.getName().equals("package")) { final TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestQueriesPackage); - final String packageName = sa.getNonConfigurationString( - R.styleable.AndroidManifestQueriesPackage_name, 0); - if (TextUtils.isEmpty(packageName)) { - return input.error("Package name is missing from package tag."); + try { + final String packageName = sa.getNonConfigurationString( + R.styleable.AndroidManifestQueriesPackage_name, 0); + if (TextUtils.isEmpty(packageName)) { + return input.error("Package name is missing from package tag."); + } + pkg.addQueriesPackage(packageName.intern()); + } finally { + sa.recycle(); } - pkg.addQueriesPackage(packageName.intern()); } else if (parser.getName().equals("provider")) { final TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestQueriesProvider); @@ -2181,6 +2202,9 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (sAconfigFlags.skipCurrentElement(parser)) { + continue; + } final ParseResult result; String tagName = parser.getName(); @@ -2769,6 +2793,9 @@ public class ParsingPackageUtils { if (type != XmlPullParser.START_TAG) { continue; } + if (sAconfigFlags.skipCurrentElement(parser)) { + continue; + } final String nodeName = parser.getName(); if (nodeName.equals("additional-certificate")) { @@ -3454,4 +3481,11 @@ public class ParsingPackageUtils { @NonNull Set<String> getInstallConstraintsAllowlist(); } + + /** + * Getter for the flags object + */ + public static AconfigFlags getAconfigFlags() { + return sAconfigFlags; + } } diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index ee33eb4f014b..37b72880dd0c 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -131,8 +131,13 @@ public class PerfettoProtoLogImpl implements IProtoLog { Runnable cacheUpdater ) { Producer.init(InitArguments.DEFAULTS); - mDataSource.register(new DataSourceParams( - DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + DataSourceParams + .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .build(); + mDataSource.register(params); this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider; this.mViewerConfigReader = viewerConfigReader; this.mLogGroups = logGroups; @@ -186,8 +191,6 @@ public class PerfettoProtoLogImpl implements IProtoLog { } os.end(outProtologViewerConfigToken); - - ctx.flush(); } catch (IOException e) { Log.e(LOG_TAG, "Failed to read ProtoLog viewer config to dump on tracing end", e); } diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java new file mode 100644 index 000000000000..4a3dfbe9c0d3 --- /dev/null +++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.ravenwood; + +/** + * Class to interact with the Ravenwood environment. + */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +public class RavenwoodEnvironment { + private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment(); + + private RavenwoodEnvironment() { + } + + /** + * @return the singleton instance. + */ + public static RavenwoodEnvironment getInstance() { + return sInstance; + } + + /** + * USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment. + * + * <p>Using this allows code to behave differently on a real device and on Ravenwood, but + * generally speaking, that's a bad idea because we want the test target code to behave + * differently. + * + * <p>This should be only used when different behavior is absolutely needed. + * + * <p>If someone needs it without having access to the SDK, the following hack would work too. + * <code>System.getProperty("java.class.path").contains("ravenwood")</code> + */ + @android.ravenwood.annotation.RavenwoodReplace + public boolean isRunningOnRavenwood() { + return false; + } + + private boolean isRunningOnRavenwood$ravenwood() { + return true; + } +} 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/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java index 6cf241e65d00..ddbb586f150e 100644 --- a/core/java/com/android/internal/util/ProcFileReader.java +++ b/core/java/com/android/internal/util/ProcFileReader.java @@ -89,6 +89,12 @@ public class ProcFileReader implements Closeable { mTail -= count; if (mTail == 0) { fillBuf(); + + if (mTail > 0 && mBuffer[0] == ' ') { + // After filling the buffer, it contains more consecutive + // delimiters that need to be skipped. + consumeBuf(0); + } } } 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/java/com/android/internal/widget/CompactMessagingLayout.java b/core/java/com/android/internal/widget/CompactMessagingLayout.java new file mode 100644 index 000000000000..1e2c01ae4be9 --- /dev/null +++ b/core/java/com/android/internal/widget/CompactMessagingLayout.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import android.app.Notification; +import android.app.Notification.MessagingStyle; +import android.app.Person; +import android.content.Context; +import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.RemotableViewMethod; +import android.view.View; +import android.view.ViewStub; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RemoteViews; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * A custom-built layout for the compact Heads Up of Notification.MessagingStyle . + */ +@RemoteViews.RemoteView +public class CompactMessagingLayout extends FrameLayout { + + private final PeopleHelper mPeopleHelper = new PeopleHelper(); + + private ViewStub mConversationFacePileViewStub; + + private int mNotificationBackgroundColor; + private int mFacePileSize; + private int mFacePileAvatarSize; + private int mFacePileProtectionWidth; + private int mLayoutColor; + + public CompactMessagingLayout(@NonNull Context context) { + super(context); + } + + public CompactMessagingLayout(@NonNull Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public CompactMessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public CompactMessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs, + int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mPeopleHelper.init(getContext()); + mConversationFacePileViewStub = requireViewById(R.id.conversation_face_pile); + mFacePileSize = getResources() + .getDimensionPixelSize(R.dimen.conversation_compact_face_pile_size); + mFacePileAvatarSize = getResources() + .getDimensionPixelSize(R.dimen.conversation_compact_face_pile_avatar_size); + mFacePileProtectionWidth = getResources().getDimensionPixelSize( + R.dimen.conversation_compact_face_pile_protection_width); + } + + /** + * Set conversation data + * + * @param extras Bundle contains conversation data + */ + @RemotableViewMethod(asyncImpl = "setGroupFacePileAsync") + public void setGroupFacePile(Bundle extras) { + // NO-OP + } + + /** + * async version of {@link ConversationLayout#setLayoutColor} + */ + @RemotableViewMethod + public Runnable setLayoutColorAsync(int color) { + mLayoutColor = color; + return NotificationRunnables.NOOP; + } + + @RemotableViewMethod(asyncImpl = "setLayoutColorAsync") + public void setLayoutColor(int color) { + mLayoutColor = color; + } + + /** + * @param color the color of the notification background + */ + @RemotableViewMethod + public void setNotificationBackgroundColor(int color) { + mNotificationBackgroundColor = color; + } + + /** + * async version of {@link CompactMessagingLayout#setGroupFacePile} + * setGroupFacePile! + */ + public Runnable setGroupFacePileAsync(Bundle extras) { + final Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES); + final List<Notification.MessagingStyle.Message> newMessages = + Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages); + final Parcelable[] histMessages = + extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES); + final List<Notification.MessagingStyle.Message> newHistoricMessages = + Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages); + final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class); + + final List<List<MessagingStyle.Message>> groups = groupMessages(newMessages, + newHistoricMessages); + final PeopleHelper.NameToPrefixMap nameToPrefixMap = + mPeopleHelper.mapUniqueNamesToPrefixWithGroupList(groups); + final int layoutColor = mLayoutColor; + // Find last two person's icon to show them in the face pile. + Icon secondLastIcon = null; + Icon lastIcon = null; + CharSequence lastKey = null; + final CharSequence userKey = getPersonKey(user); + for (int i = groups.size() - 1; i >= 0; i--) { + final MessagingStyle.Message message = groups.get(i).get(0); + final Person sender = + message.getSenderPerson() != null ? message.getSenderPerson() : user; + final CharSequence senderKey = getPersonKey(sender); + final boolean notUser = senderKey != userKey; + final boolean notIncluded = senderKey != lastKey; + + if ((notUser && notIncluded) || (i == 0 && lastKey == null)) { + final Icon icon = getSenderIcon(sender, nameToPrefixMap, layoutColor); + if (lastIcon == null) { + lastIcon = icon; + lastKey = senderKey; + } else { + secondLastIcon = icon; + break; + } + } + } + + if (lastIcon == null) { + lastIcon = getSenderIcon(null, null, layoutColor); + } + + if (secondLastIcon == null) { + secondLastIcon = getSenderIcon(null, null, layoutColor); + } + final Drawable secondLastIconDrawable = secondLastIcon.loadDrawable(getContext()); + final Drawable lastIconDrawable = lastIcon.loadDrawable(getContext()); + return () -> { + final View conversationFacePile = mConversationFacePileViewStub.inflate(); + conversationFacePile.setVisibility(VISIBLE); + + final ImageView facePileBottomBg = conversationFacePile.requireViewById( + com.android.internal.R.id.conversation_face_pile_bottom_background); + final ImageView facePileTop = conversationFacePile.requireViewById( + com.android.internal.R.id.conversation_face_pile_top); + final ImageView facePileBottom = conversationFacePile.requireViewById( + com.android.internal.R.id.conversation_face_pile_bottom); + + facePileTop.setImageDrawable(secondLastIconDrawable); + facePileBottom.setImageDrawable(lastIconDrawable); + facePileBottomBg.setImageTintList(ColorStateList.valueOf(mNotificationBackgroundColor)); + setSize(conversationFacePile, mFacePileSize); + setSize(facePileBottom, mFacePileAvatarSize); + setSize(facePileTop, mFacePileAvatarSize); + setSize(facePileBottomBg, mFacePileAvatarSize + 2 * mFacePileProtectionWidth); + }; + } + + @NonNull + private Icon getSenderIcon(@Nullable Person sender, + @Nullable PeopleHelper.NameToPrefixMap uniqueNames, + int layoutColor) { + if (sender == null) { + return mPeopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "", + layoutColor); + } + + if (sender.getIcon() != null) { + return sender.getIcon(); + } + + final CharSequence senderName = sender.getName(); + if (!TextUtils.isEmpty(senderName)) { + final String symbol = uniqueNames != null ? uniqueNames.getPrefix(senderName) : ""; + return mPeopleHelper.createAvatarSymbol(senderName, symbol, layoutColor); + } + + return mPeopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "", layoutColor); + } + + + /** + * Groups the given messages by their sender. + */ + private static List<List<MessagingStyle.Message>> groupMessages( + List<MessagingStyle.Message> messages, + List<MessagingStyle.Message> historicMessages + ) { + if (messages.isEmpty() && historicMessages.isEmpty()) return List.of(); + + ArrayList<MessagingStyle.Message> currentGroup = null; + CharSequence currentSenderKey = null; + final ArrayList<List<MessagingStyle.Message>> groups = new ArrayList<>(); + final int histSize = historicMessages.size(); + + for (int i = 0; i < histSize + messages.size(); i++) { + final MessagingStyle.Message message = i < histSize ? historicMessages.get(i) + : messages.get(i - histSize); + if (message == null) continue; + + final CharSequence senderKey = getPersonKey(message.getSenderPerson()); + final boolean isNewGroup = currentGroup == null || senderKey != currentSenderKey; + if (isNewGroup) { + currentGroup = new ArrayList<>(); + groups.add(currentGroup); + currentSenderKey = senderKey; + } + currentGroup.add(message); + } + return groups; + } + + private static CharSequence getPersonKey(@Nullable Person person) { + return person == null ? null : person.getKey() == null ? person.getName() : person.getKey(); + } + + private static void setSize(View view, int size) { + final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams(); + lp.width = size; + lp.height = size; + view.setLayoutParams(lp); + } +} diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index 66b0158fbd67..0734e6827d4d 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -886,9 +886,16 @@ public class LockPatternView extends View { cellState.activationAnimator.cancel(); } AnimatorSet animatorSet = new AnimatorSet(); + + // When running the line end animation (see doc for createLineEndAnimation), if cell is in: + // - activate state - use finger position at the time of hit detection + // - deactivate state - use current position where the end was last during initial animation + // Note that deactivate state will only come if mKeepDotActivated is themed true. + final float startX = activate == CELL_ACTIVATE ? mInProgressX : cellState.lineEndX; + final float startY = activate == CELL_ACTIVATE ? mInProgressY : cellState.lineEndY; AnimatorSet.Builder animatorSetBuilder = animatorSet .play(createLineDisappearingAnimation()) - .with(createLineEndAnimation(cellState, mInProgressX, mInProgressY, + .with(createLineEndAnimation(cellState, startX, startY, getCenterXForColumn(cell.column), getCenterYForRow(cell.row))); if (mDotSize != mDotSizeActivated) { animatorSetBuilder.with(createDotRadiusAnimation(cellState)); diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java index e07acac52f2c..3d8237ea5389 100644 --- a/core/java/com/android/internal/widget/MessagingLinearLayout.java +++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java @@ -16,6 +16,8 @@ package com.android.internal.widget; +import static android.widget.flags.Flags.messagingChildRequestLayout; + import android.annotation.Nullable; import android.annotation.Px; import android.content.Context; @@ -92,6 +94,10 @@ public class MessagingLinearLayout extends ViewGroup { final View child = getChildAt(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); lp.hide = true; + // Child always needs to be measured to calculate hide property correctly in onMeasure. + if (messagingChildRequestLayout()) { + child.requestLayout(); + } if (child instanceof MessagingChild) { MessagingChild messagingChild = (MessagingChild) child; // Whenever we encounter the message first, it's always first in the layout diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 4031b2fd335c..0f4615a12ea2 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -16,18 +16,27 @@ package com.android.internal.widget; +import android.annotation.Nullable; import android.app.Flags; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; import android.util.AttributeSet; +import android.view.RemotableViewMethod; import android.widget.RemoteViews; -import androidx.annotation.Nullable; - /** * An image view that holds the icon displayed on the left side of a notification row. */ @RemoteViews.RemoteView public class NotificationRowIconView extends CachingIconView { + private boolean mApplyCircularCrop = false; + public NotificationRowIconView(Context context) { super(context); } @@ -57,4 +66,82 @@ public class NotificationRowIconView extends CachingIconView { super.onFinishInflate(); } + + @Nullable + @Override + Drawable loadSizeRestrictedIcon(@Nullable Icon icon) { + final Drawable original = super.loadSizeRestrictedIcon(icon); + final Drawable result; + if (mApplyCircularCrop) { + result = makeCircularDrawable(original); + } else { + result = original; + } + + return result; + } + + /** + * Enables circle crop that makes given image circular + */ + @RemotableViewMethod(asyncImpl = "setApplyCircularCropAsync") + public void setApplyCircularCrop(boolean applyCircularCrop) { + mApplyCircularCrop = applyCircularCrop; + } + + /** + * Async version of {@link NotificationRowIconView#setApplyCircularCrop} + */ + public Runnable setApplyCircularCropAsync(boolean applyCircularCrop) { + mApplyCircularCrop = applyCircularCrop; + return () -> { + }; + } + + @Nullable + private Drawable makeCircularDrawable(@Nullable Drawable original) { + if (original == null) { + return original; + } + + final Bitmap source = drawableToBitmap(original); + + int size = Math.min(source.getWidth(), source.getHeight()); + + Bitmap squared = Bitmap.createScaledBitmap(source, size, size, /* filter= */ false); + Bitmap result = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888); + + final Canvas canvas = new Canvas(result); + final Paint paint = new Paint(); + paint.setShader( + new BitmapShader(squared, BitmapShader.TileMode.CLAMP, + BitmapShader.TileMode.CLAMP)); + paint.setAntiAlias(true); + float radius = size / 2f; + canvas.drawCircle(radius, radius, radius, paint); + return new BitmapDrawable(getResources(), result); + } + + private static Bitmap drawableToBitmap(Drawable drawable) { + if (drawable instanceof BitmapDrawable bitmapDrawable) { + final Bitmap bitmap = bitmapDrawable.getBitmap(); + if (bitmap.getConfig() == Bitmap.Config.HARDWARE) { + return bitmap.copy(Bitmap.Config.ARGB_8888, false); + } else { + return bitmap; + } + } + + int width = drawable.getIntrinsicWidth(); + width = width > 0 ? width : 1; + int height = drawable.getIntrinsicHeight(); + height = height > 0 ? height : 1; + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return bitmap; + } } diff --git a/core/java/com/android/internal/widget/TEST_MAPPING b/core/java/com/android/internal/widget/TEST_MAPPING new file mode 100644 index 000000000000..91cecfda7a16 --- /dev/null +++ b/core/java/com/android/internal/widget/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + // v2/sysui/suite/test-mapping-sysui-screenshot-test + "sysui-screenshot-test": [ + { + "name": "SystemUIGoogleScreenshotTests", + "options": [ + { + "exclude-annotation": "org.junit.Ignore" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.Postsubmit" + } + ] + } + ] +}
\ No newline at end of file diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 80a75999c3d0..61eaa526116c 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -439,6 +439,7 @@ cc_library_shared_for_libandroid_runtime { "android_database_SQLiteConnection.cpp", "android_database_SQLiteGlobal.cpp", "android_database_SQLiteDebug.cpp", + "android_database_SQLiteRawStatement.cpp", "android_hardware_input_InputApplicationHandle.cpp", "android_os_MessageQueue.cpp", "android_os_Parcel.cpp", @@ -483,4 +484,8 @@ cc_library_shared { "libnativehelper", "libvintf", ], + + required: [ + "vintf", + ], } diff --git a/core/jni/android_hardware_UsbDeviceConnection.cpp b/core/jni/android_hardware_UsbDeviceConnection.cpp index 7267eb8cb3dc..a0228428e90e 100644 --- a/core/jni/android_hardware_UsbDeviceConnection.cpp +++ b/core/jni/android_hardware_UsbDeviceConnection.cpp @@ -190,21 +190,17 @@ android_hardware_UsbDeviceConnection_bulk_request(JNIEnv *env, jobject thiz, return -1; } - bool is_dir_in = (endpoint & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN; - jbyte *bufferBytes = (jbyte *)malloc(length); - - if (!is_dir_in && buffer) { - env->GetByteArrayRegion(buffer, start, length, bufferBytes); + jbyte* bufferBytes = NULL; + if (buffer) { + bufferBytes = (jbyte*)env->GetPrimitiveArrayCritical(buffer, NULL); } - jint result = usb_device_bulk_transfer(device, endpoint, bufferBytes, length, timeout); + jint result = usb_device_bulk_transfer(device, endpoint, bufferBytes + start, length, timeout); - if (is_dir_in && buffer) { - env->SetByteArrayRegion(buffer, start, length, bufferBytes); + if (bufferBytes) { + env->ReleasePrimitiveArrayCritical(buffer, bufferBytes, 0); } - free(bufferBytes); - return result; } 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_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp index 0e3c51047b31..c7866524668f 100644 --- a/core/jni/android_os_HwParcel.cpp +++ b/core/jni/android_os_HwParcel.cpp @@ -235,6 +235,10 @@ bool JHwParcel::wasSent() const { return mWasSent; } +void JHwParcel::addBlob(const sp<JHwBlob> &blob) { + mBlobs.emplace_back(blob); +} + } // namespace android //////////////////////////////////////////////////////////////////////////////// @@ -1059,6 +1063,7 @@ static void JHwParcel_native_writeBuffer( JHwParcel::GetNativeContext(env, thiz)->getParcel(); sp<JHwBlob> blob = JHwBlob::GetNativeContext(env, blobObj); + JHwParcel::GetNativeContext(env, thiz)->addBlob(blob); status_t err = blob->writeToParcel(parcel); if (err != OK) { diff --git a/core/jni/android_os_HwParcel.h b/core/jni/android_os_HwParcel.h index 2c26993a0439..07d144a7ef13 100644 --- a/core/jni/android_os_HwParcel.h +++ b/core/jni/android_os_HwParcel.h @@ -17,14 +17,15 @@ #ifndef ANDROID_OS_HW_PARCEL_H #define ANDROID_OS_HW_PARCEL_H -#include "hwbinder/EphemeralStorage.h" - #include <android-base/macros.h> #include <hwbinder/IBinder.h> #include <hwbinder/Parcel.h> #include <jni.h> #include <utils/RefBase.h> +#include "android_os_HwBlob.h" +#include "hwbinder/EphemeralStorage.h" + namespace android { struct JHwParcel : public RefBase { @@ -44,6 +45,8 @@ struct JHwParcel : public RefBase { EphemeralStorage *getStorage(); + void addBlob(const sp<JHwBlob> &blob); + void setTransactCallback(::android::hardware::IBinder::TransactCallback cb); void send(); @@ -60,6 +63,7 @@ private: ::android::hardware::IBinder::TransactCallback mTransactCallback; bool mWasSent; + std::vector<sp<JHwBlob>> mBlobs; DISALLOW_COPY_AND_ASSIGN(JHwParcel); }; diff --git a/core/jni/android_os_VintfObject.cpp b/core/jni/android_os_VintfObject.cpp index 8dc9d0aa578e..7a4854bbd260 100644 --- a/core/jni/android_os_VintfObject.cpp +++ b/core/jni/android_os_VintfObject.cpp @@ -32,6 +32,8 @@ static jmethodID gHashMapInit; static jmethodID gHashMapPut; static jclass gLongClazz; static jmethodID gLongValueOf; +static jclass gVintfObjectClazz; +static jmethodID gRunCommand; namespace android { @@ -47,6 +49,56 @@ using vintf::VintfObject; using vintf::Vndk; using vintf::CheckFlags::ENABLE_ALL_CHECKS; +// Instead of VintfObject::GetXxx(), we construct +// HalManifest/CompatibilityMatrix objects by calling `vintf` through +// UiAutomation.executeShellCommand() so that the commands are executed +// using shell identity. Otherwise, we would need to allow "apps" to access +// files like apex-info-list.xml which we don't want to open to apps. +// This is okay because VintfObject is @TestApi and only used in CTS tests. + +static std::string runCmd(JNIEnv* env, const char* cmd) { + jstring jstr = (jstring)env->CallStaticObjectMethod(gVintfObjectClazz, gRunCommand, + env->NewStringUTF(cmd)); + std::string output; + if (jstr) { + auto cstr = env->GetStringUTFChars(jstr, nullptr); + output = std::string(cstr); + env->ReleaseStringUTFChars(jstr, cstr); + } else { + LOG(WARNING) << "Failed to run " << cmd; + env->ExceptionDescribe(); + env->ExceptionClear(); + } + return output; +} + +template <typename T> +static std::shared_ptr<const T> fromXml(const std::string& content) { + std::shared_ptr<T> object = std::make_unique<T>(); + std::string error; + if (fromXml(object.get(), content, &error)) { + return object; + } + LOG(WARNING) << "Unabled to parse: " << error; + return nullptr; +} + +static std::shared_ptr<const HalManifest> getDeviceHalManifest(JNIEnv* env) { + return fromXml<HalManifest>(runCmd(env, "vintf dm")); +} + +static std::shared_ptr<const HalManifest> getFrameworkHalManifest(JNIEnv* env) { + return fromXml<HalManifest>(runCmd(env, "vintf fm")); +} + +static std::shared_ptr<const CompatibilityMatrix> getDeviceCompatibilityMatrix(JNIEnv* env) { + return fromXml<CompatibilityMatrix>(runCmd(env, "vintf dcm")); +} + +static std::shared_ptr<const CompatibilityMatrix> getFrameworkCompatibilityMatrix(JNIEnv* env) { + return fromXml<CompatibilityMatrix>(runCmd(env, "vintf fcm")); +} + template<typename V> static inline jobjectArray toJavaStringArray(JNIEnv* env, const V& v) { size_t i; @@ -83,12 +135,10 @@ static jobjectArray android_os_VintfObject_report(JNIEnv* env, jclass) { std::vector<std::string> cStrings; - tryAddSchema(VintfObject::GetDeviceHalManifest(), "device manifest", &cStrings); - tryAddSchema(VintfObject::GetFrameworkHalManifest(), "framework manifest", &cStrings); - tryAddSchema(VintfObject::GetDeviceCompatibilityMatrix(), "device compatibility matrix", - &cStrings); - tryAddSchema(VintfObject::GetFrameworkCompatibilityMatrix(), "framework compatibility matrix", - &cStrings); + tryAddSchema(getDeviceHalManifest(env), "device manifest", &cStrings); + tryAddSchema(getFrameworkHalManifest(env), "framework manifest", &cStrings); + tryAddSchema(getDeviceCompatibilityMatrix(env), "device compatibility matrix", &cStrings); + tryAddSchema(getFrameworkCompatibilityMatrix(env), "framework compatibility matrix", &cStrings); return toJavaStringArray(env, cStrings); } @@ -108,15 +158,13 @@ static jint android_os_VintfObject_verifyBuildAtBoot(JNIEnv*, jclass) { static jobjectArray android_os_VintfObject_getHalNamesAndVersions(JNIEnv* env, jclass) { std::set<std::string> halNames; - tryAddHalNamesAndVersions(VintfObject::GetDeviceHalManifest(), - "device manifest", &halNames); - tryAddHalNamesAndVersions(VintfObject::GetFrameworkHalManifest(), - "framework manifest", &halNames); + tryAddHalNamesAndVersions(getDeviceHalManifest(env), "device manifest", &halNames); + tryAddHalNamesAndVersions(getFrameworkHalManifest(env), "framework manifest", &halNames); return toJavaStringArray(env, halNames); } static jstring android_os_VintfObject_getSepolicyVersion(JNIEnv* env, jclass) { - std::shared_ptr<const HalManifest> manifest = VintfObject::GetDeviceHalManifest(); + std::shared_ptr<const HalManifest> manifest = getDeviceHalManifest(env); if (manifest == nullptr || manifest->type() != SchemaType::DEVICE) { LOG(WARNING) << __FUNCTION__ << "Cannot get device manifest"; return nullptr; @@ -126,8 +174,7 @@ static jstring android_os_VintfObject_getSepolicyVersion(JNIEnv* env, jclass) { } static jstring android_os_VintfObject_getPlatformSepolicyVersion(JNIEnv* env, jclass) { - std::shared_ptr<const CompatibilityMatrix> matrix = - VintfObject::GetFrameworkCompatibilityMatrix(); + std::shared_ptr<const CompatibilityMatrix> matrix = getFrameworkCompatibilityMatrix(env); if (matrix == nullptr || matrix->type() != SchemaType::FRAMEWORK) { jniThrowRuntimeException(env, "Cannot get framework compatibility matrix"); return nullptr; @@ -148,7 +195,7 @@ static jstring android_os_VintfObject_getPlatformSepolicyVersion(JNIEnv* env, jc } static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) { - std::shared_ptr<const HalManifest> manifest = VintfObject::GetFrameworkHalManifest(); + std::shared_ptr<const HalManifest> manifest = getFrameworkHalManifest(env); if (manifest == nullptr || manifest->type() != SchemaType::FRAMEWORK) { LOG(WARNING) << __FUNCTION__ << "Cannot get framework manifest"; return nullptr; @@ -163,7 +210,7 @@ static jobject android_os_VintfObject_getVndkSnapshots(JNIEnv* env, jclass) { } static jobject android_os_VintfObject_getTargetFrameworkCompatibilityMatrixVersion(JNIEnv* env, jclass) { - std::shared_ptr<const HalManifest> manifest = VintfObject::GetDeviceHalManifest(); + std::shared_ptr<const HalManifest> manifest = getDeviceHalManifest(env); if (manifest == nullptr || manifest->level() == Level::UNSPECIFIED) { return nullptr; } @@ -188,19 +235,20 @@ static const JNINativeMethod gVintfObjectMethods[] = { const char* const kVintfObjectPathName = "android/os/VintfObject"; -int register_android_os_VintfObject(JNIEnv* env) -{ - +int register_android_os_VintfObject(JNIEnv* env) { gString = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/String")); gHashMapClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/util/HashMap")); gHashMapInit = GetMethodIDOrDie(env, gHashMapClazz, "<init>", "()V"); - gHashMapPut = GetMethodIDOrDie(env, gHashMapClazz, - "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + gHashMapPut = GetMethodIDOrDie(env, gHashMapClazz, "put", + "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); gLongClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, "java/lang/Long")); gLongValueOf = GetStaticMethodIDOrDie(env, gLongClazz, "valueOf", "(J)Ljava/lang/Long;"); + gVintfObjectClazz = MakeGlobalRefOrDie(env, FindClassOrDie(env, kVintfObjectPathName)); + gRunCommand = GetStaticMethodIDOrDie(env, gVintfObjectClazz, "runShellCommand", + "(Ljava/lang/String;)Ljava/lang/String;"); return RegisterMethodsOrDie(env, kVintfObjectPathName, gVintfObjectMethods, - NELEM(gVintfObjectMethods)); + NELEM(gVintfObjectMethods)); } extern int register_android_os_VintfRuntimeInfo(JNIEnv* env); diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp index f82ebfe8c947..17129d8913d7 100644 --- a/core/jni/android_tracing_PerfettoDataSource.cpp +++ b/core/jni/android_tracing_PerfettoDataSource.cpp @@ -244,8 +244,8 @@ static jlong nativeGetFinalizer(JNIEnv* /* env */, jclass /* clazz */) { return static_cast<jlong>(reinterpret_cast<uintptr_t>(&nativeDestroy)); } -void nativeFlush(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) { - ALOG(LOG_DEBUG, LOG_TAG, "nativeFlush(%p)", (void*)ds_ptr); +void nativeWritePackets(JNIEnv* env, jclass clazz, jlong ds_ptr, jobjectArray packets) { + ALOG(LOG_DEBUG, LOG_TAG, "nativeWritePackets(%p)", (void*)ds_ptr); sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(ds_ptr); datasource->WritePackets(env, packets); } @@ -256,10 +256,12 @@ void nativeFlushAll(JNIEnv* env, jclass clazz, jlong ptr) { } void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr, - jint buffer_exhausted_policy) { + jint buffer_exhausted_policy, jboolean will_notify_on_stop, + jboolean no_flush) { sp<PerfettoDataSource> datasource = reinterpret_cast<PerfettoDataSource*>(datasource_ptr); struct PerfettoDsParams params = PerfettoDsParamsDefault(); + params.will_notify_on_stop = will_notify_on_stop; params.buffer_exhausted_policy = (PerfettoDsBufferExhaustedPolicy)buffer_exhausted_policy; params.user_arg = reinterpret_cast<void*>(datasource.get()); @@ -325,13 +327,15 @@ void nativeRegisterDataSource(JNIEnv* env, jclass clazz, jlong datasource_ptr, datasource_instance->onStart(env); }; - params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, void* inst_ctx, - struct PerfettoDsOnFlushArgs*) { - JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); + if (!no_flush) { + params.on_flush_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex, void*, + void* inst_ctx, struct PerfettoDsOnFlushArgs*) { + JNIEnv* env = GetOrAttachJNIEnvironment(gVm, JNI_VERSION_1_6); - auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx); - datasource_instance->onFlush(env); - }; + auto* datasource_instance = static_cast<PerfettoDataSourceInstance*>(inst_ctx); + datasource_instance->onFlush(env); + }; + } params.on_stop_cb = [](struct PerfettoDsImpl*, PerfettoDsInstanceIndex inst_id, void* user_arg, void* inst_ctx, struct PerfettoDsOnStopArgs*) { @@ -422,7 +426,7 @@ const JNINativeMethod gMethods[] = { (void*)nativeCreate}, {"nativeFlushAll", "(J)V", (void*)nativeFlushAll}, {"nativeGetFinalizer", "()J", (void*)nativeGetFinalizer}, - {"nativeRegisterDataSource", "(JI)V", (void*)nativeRegisterDataSource}, + {"nativeRegisterDataSource", "(JIZZ)V", (void*)nativeRegisterDataSource}, {"nativeGetPerfettoInstanceLocked", "(JI)Landroid/tracing/perfetto/DataSourceInstance;", (void*)nativeGetPerfettoInstanceLocked}, {"nativeReleasePerfettoInstanceLocked", "(JI)V", @@ -431,11 +435,12 @@ const JNINativeMethod gMethods[] = { {"nativePerfettoDsTraceIterateBegin", "(J)Z", (void*)nativePerfettoDsTraceIterateBegin}, {"nativePerfettoDsTraceIterateNext", "(J)Z", (void*)nativePerfettoDsTraceIterateNext}, {"nativePerfettoDsTraceIterateBreak", "(J)V", (void*)nativePerfettoDsTraceIterateBreak}, - {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex}}; + {"nativeGetPerfettoDsInstanceIndex", "(J)I", (void*)nativeGetPerfettoDsInstanceIndex}, + + {"nativeWritePackets", "(J[[B)V", (void*)nativeWritePackets}}; const JNINativeMethod gMethodsTracingContext[] = { /* name, signature, funcPtr */ - {"nativeFlush", "(J[[B)V", (void*)nativeFlush}, {"nativeGetCustomTls", "(J)Ljava/lang/Object;", (void*)nativeGetCustomTls}, {"nativeGetIncrementalState", "(J)Ljava/lang/Object;", (void*)nativeGetIncrementalState}, {"nativeSetCustomTls", "(JLjava/lang/Object;)V", (void*)nativeSetCustomTls}, diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index aae0da9006a2..f5992d906323 100644 --- a/core/jni/android_view_InputDevice.cpp +++ b/core/jni/android_view_InputDevice.cpp @@ -90,7 +90,8 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor(), deviceInfo.hasBattery(), usiVersion.majorVersion, usiVersion.minorVersion, - deviceInfo.getAssociatedDisplayId())); + deviceInfo.getAssociatedDisplayId(), + deviceInfo.isEnabled())); // Note: We do not populate the Bluetooth address into the InputDevice object to avoid leaking // it to apps that do not have the Bluetooth permission. @@ -126,7 +127,7 @@ int register_android_view_InputDevice(JNIEnv* env) gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>", "(IIILjava/lang/String;IIILjava/lang/" "String;ZIILandroid/view/KeyCharacterMap;Ljava/" - "lang/String;Ljava/lang/String;ZZZZZIII)V"); + "lang/String;Ljava/lang/String;ZZZZZIIIZ)V"); gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V"); 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..59d18b8535f5 100644 --- a/core/jni/platform/host/HostRuntime.cpp +++ b/core/jni/platform/host/HostRuntime.cpp @@ -89,6 +89,7 @@ extern int register_android_database_CursorWindow(JNIEnv* env); extern int register_android_database_SQLiteConnection(JNIEnv* env); extern int register_android_database_SQLiteGlobal(JNIEnv* env); extern int register_android_database_SQLiteDebug(JNIEnv* env); +extern int register_android_database_SQLiteRawStatement(JNIEnv* env); extern int register_android_os_FileObserver(JNIEnv* env); extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_Parcel(JNIEnv* env); @@ -128,6 +129,8 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { REG_JNI(register_android_database_SQLiteConnection)}, {"android.database.sqlite.SQLiteGlobal", REG_JNI(register_android_database_SQLiteGlobal)}, {"android.database.sqlite.SQLiteDebug", REG_JNI(register_android_database_SQLiteDebug)}, + {"android.database.sqlite.SQLiteRawStatement", + REG_JNI(register_android_database_SQLiteRawStatement)}, #endif {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)}, {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)}, @@ -329,7 +332,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::LogicalDisplayId::DEFAULT); info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC); info.setKeyCharacterMap(*charMap); diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto index c92435f61ab1..654d83c827c9 100644 --- a/core/proto/android/server/windowmanagerservice.proto +++ b/core/proto/android/server/windowmanagerservice.proto @@ -607,7 +607,7 @@ message ImeInsetsSourceProviderProto { optional InsetsSourceProviderProto insets_source_provider = 1; optional WindowStateProto ime_target_from_ime = 2; - optional bool is_ime_layout_drawn = 3; + optional bool is_ime_layout_drawn = 3 [deprecated=true]; } message BackNavigationProto { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 77a99120072e..8541704ecfb9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -178,6 +178,7 @@ <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" /> <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" /> <protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" /> + <protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" /> <protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" /> <protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" /> <protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" /> @@ -298,6 +299,9 @@ <protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" /> + <protected-broadcast android:name="android.hardware.hdmi.action.OSD_MESSAGE" /> + <protected-broadcast android:name="android.hardware.hdmi.action.ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI" /> + <protected-broadcast android:name="android.hardware.usb.action.USB_STATE" /> <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" /> <protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" /> @@ -8770,6 +8774,7 @@ <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncInCallService" android:permission="android.permission.BIND_INCALL_SERVICE" + android:enabled="@bool/config_enableContextSyncInCall" android:exported="true"> <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS" android:value="true" /> 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..c692967d36b3 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="M21,5 V21 H5 z"> + <!-- 1 bar. move to higher ground. --> + <path + android:name="ic_signal_cellular_1_4_bar" + android:fillColor="@android:color/white" + android:pathData="M0,0 H11 V24 H0 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..b01c26972ac8 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="M21,5 V21 H5 z"> + <!-- 1 bar. might have to call you back. --> + <path + android:name="ic_signal_cellular_1_5_bar" + android:fillColor="@android:color/white" + android:pathData="M0,0 H12 V24 H0 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..982623d41060 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="M21,5 V21 H5 z"> + <!-- 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="M0,0 H14 V24 H0 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..75daadd213dc 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="M21,5 V21 H5 z"> + <!-- 2 bars. hanging in there. --> + <path + android:name="ic_signal_cellular_2_5_bar" + android:fillColor="@android:color/white" + android:pathData="M0,0 H14 V24 H0 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..4e4bea397d26 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="M21,5 V21 H5 z"> + <!-- 3 bars. quite nice. --> + <path + android:name="ic_signal_cellular_3_4_bar" + android:fillColor="@android:color/white" + android:pathData="M0,0 H17 V24 H0 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..9a98c2982574 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="M21,5 V21 H5 z"> + <!-- 3 bars. not great, not terrible. --> + <path + android:name="ic_signal_cellular_3_5_bar" + android:fillColor="@android:color/white" + android:pathData="M0,0 H16 V24 H0 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..2a37d011c650 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="M21,5 V21 H5 z"> + <!-- 4 bars. extremely respectable. --> + <path + android:name="ic_signal_cellular_4_5_bar" + android:fillColor="@android:color/white" + android:pathData="M0,0 H18 V24 H0 z" /> + </clip-path> </vector>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_thread_network.xml b/core/res/res/drawable/ic_thread_network.xml new file mode 100644 index 000000000000..1d7608f3daa2 --- /dev/null +++ b/core/res/res/drawable/ic_thread_network.xml @@ -0,0 +1,25 @@ +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="960" + android:viewportHeight="960" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M476,880Q394,879 322,847.5Q250,816 196,761.5Q142,707 111,634.5Q80,562 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,623 791.5,732.5Q703,842 563,871L563,480L607,480Q661,480 699.5,441.5Q738,403 738,349Q738,295 699.5,256.5Q661,218 607,218Q553,218 514.5,256.5Q476,295 476,349L476,393L345,393Q279,393 233,439Q187,485 187,551Q187,617 233,662.5Q279,708 345,708L345,621Q316,621 295,600.5Q274,580 274,551Q274,522 295,501Q316,480 345,480L476,480L476,880ZM563,393L563,349Q563,331 576,318Q589,305 607,305Q625,305 638,318Q651,331 651,349Q651,367 638,380Q625,393 607,393L563,393Z"/> +</vector> diff --git a/core/res/res/layout/miniresolver.xml b/core/res/res/layout/miniresolver.xml index db0ea547fbd5..e60e0b0079a9 100644 --- a/core/res/res/layout/miniresolver.xml +++ b/core/res/res/layout/miniresolver.xml @@ -33,7 +33,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alwaysShow="true" - android:elevation="@dimen/resolver_elevation" android:paddingTop="24dp" android:paddingStart="@dimen/resolver_edge_margin" android:paddingEnd="@dimen/resolver_edge_margin" diff --git a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml new file mode 100644 index 000000000000..82920bad95cd --- /dev/null +++ b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<com.android.internal.widget.CompactMessagingLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/status_bar_latest_event_content" + android:layout_width="match_parent" + android:layout_height="@dimen/notification_header_height" + android:clipChildren="false" + android:tag="compactMessagingHUN" + android:gravity="center_vertical" + android:theme="@style/Theme.DeviceDefault.Notification" + android:importantForAccessibility="no"> + <com.android.internal.widget.NotificationRowIconView + android:id="@+id/icon" + android:layout_width="@dimen/notification_icon_circle_size" + android:layout_height="@dimen/notification_icon_circle_size" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="@dimen/notification_icon_circle_start" + android:background="@drawable/notification_icon_circle" + android:padding="@dimen/notification_icon_circle_padding" + android:maxDrawableWidth="@dimen/notification_icon_circle_size" + android:maxDrawableHeight="@dimen/notification_icon_circle_size" + /> + <com.android.internal.widget.NotificationRowIconView + android:id="@+id/conversation_icon" + android:layout_width="@dimen/notification_icon_circle_size" + android:layout_height="@dimen/notification_icon_circle_size" + android:layout_gravity="center_vertical|start" + android:layout_marginStart="@dimen/notification_icon_circle_start" + android:maxDrawableWidth="@dimen/notification_icon_circle_size" + android:maxDrawableHeight="@dimen/notification_icon_circle_size" + android:scaleType="centerCrop" + android:importantForAccessibility="no" + /> + <ViewStub + android:layout="@layout/conversation_face_pile_layout" + android:layout_gravity="center_vertical|start" + android:layout_width="@dimen/conversation_compact_face_pile_size" + android:layout_height="@dimen/conversation_compact_face_pile_size" + android:layout_marginStart="@dimen/notification_icon_circle_start" + android:id="@+id/conversation_face_pile" + /> + <FrameLayout + android:id="@+id/alternate_expand_target" + android:layout_width="@dimen/notification_content_margin_start" + android:layout_height="match_parent" + android:layout_gravity="start" + android:importantForAccessibility="no" + android:focusable="false" + /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginStart="@dimen/notification_content_margin_start" + android:orientation="horizontal" + > + <NotificationTopLineView + android:id="@+id/notification_top_line" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_centerVertical="true" + android:layout_weight="1" + android:clipChildren="false" + android:gravity="center_vertical" + android:theme="@style/Theme.DeviceDefault.Notification" + > + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/notification_header_separating_margin" + android:ellipsize="end" + android:fadingEdge="horizontal" + android:singleLine="true" + android:textAlignment="viewStart" + android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title" + /> + <include layout="@layout/notification_top_line_views" /> + </NotificationTopLineView> + <FrameLayout + android:id="@+id/reply_action_container" + android:layout_width="wrap_content" + android:layout_height="@dimen/notification_action_list_height" + android:gravity="center_vertical" + android:orientation="horizontal" /> + <FrameLayout + android:id="@+id/expand_button_touch_container" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:minWidth="@dimen/notification_content_margin_end" + > + <include layout="@layout/notification_expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical|end" + /> + </FrameLayout> + </LinearLayout> +</com.android.internal.widget.CompactMessagingLayout> diff --git a/core/res/res/layout/side_fps_toast.xml b/core/res/res/layout/side_fps_toast.xml index 96860b050393..78299ab0ea99 100644 --- a/core/res/res/layout/side_fps_toast.xml +++ b/core/res/res/layout/side_fps_toast.xml @@ -18,28 +18,28 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:minWidth="350dp" android:layout_gravity="center" + android:minWidth="350dp" android:background="@color/side_fps_toast_background"> <TextView - android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_width="0dp" + android:layout_weight="6" + android:paddingBottom="10dp" android:text="@string/fp_power_button_enrollment_title" - android:singleLine="true" - android:ellipsize="end" android:textColor="@color/side_fps_text_color" android:paddingLeft="20dp"/> <Space - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_weight="1"/> + android:layout_width="5dp" + android:layout_height="match_parent" /> <Button android:id="@+id/turn_off_screen" - android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_width="0dp" + android:layout_weight="3" + android:paddingBottom="10dp" android:text="@string/fp_power_button_enrollment_button_text" - android:paddingRight="20dp" style="?android:attr/buttonBarNegativeButtonStyle" android:textColor="@color/side_fps_button_color" - android:maxLines="1"/> + /> </LinearLayout>
\ 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 7f0e4c208540..7fe8641cd889 100644 --- a/core/res/res/values-af/strings.xml +++ b/core/res/res/values-af/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> sekondes"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nie aangestuur nie"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nie aangestuur nie"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Selnetwerksekuriteit"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Enkripsie, kennisgewings vir ongeënkripteerde netwerke"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Toegang is tot toestel-ID verkry"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"’n Nabygeleë netwerk het om <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> jou toestel se unieke ID (IMSI of IMEI) aangeteken terwyl jou <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM gebruik is"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"’n Nabygeleë netwerk het om <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> jou toestel se unieke ID (IMSI of IMEI) aangeteken terwyl jou <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM gebruik is.\n\nDit beteken dat jou ligging, aktiwiteit of identiteit aangeteken is. Dit is ’n algemene praktyk, maar kan ’n kwessie wees vir mense wat bekommerd is oor privaatheid."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Verbind aan geënkripteerde netwerk <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-verbinding is nou meer beveilig"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Verbind aan ongeënkripteerde netwerk"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Oproepe, boodskappe en data is tans meer kwesbaar terwyl jy jou <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM gebruik."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Oproepe, boodskappe en data is tans meer kwesbaar terwyl jy jou <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM gebruik.\n\nJy sal nog ’n kennisgewing kry wanneer jou verbinding weer geënkripteer is."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Instellings vir selnetwerksekuriteit"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Meer inligting"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Het dit"</string> <string name="fcComplete" msgid="1080909484660507044">"Kenmerkkode klaar."</string> <string name="fcError" msgid="5325116502080221346">"Verbindingsprobleem of ongeldige kenmerk-kode."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Stembystand"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Snelsluit"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nuwe kennisgewing"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fisieke sleutelbord"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sekuriteit"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Neem skermkiekie"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan \'n skermkiekie neem."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Voorskou, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"maak toe"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"deaktiveer of verander statusbalk"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Laat die app toe om die statusbalk te deaktiveer en stelselikone by te voeg of te verwyder."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"wees die statusbalk"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Roetinemodus-inligtingkennisgewing"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterybespaarder is aangeskakel"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Verminder tans batterygebruik om batterylewe te verleng"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterybespaarder is aan"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterybespaarder is aangeskakel om batterylewe te verleng"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterybespaarder"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterybespaarder is afgeskakel"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Foon se battery het genoeg krag. Kenmerke is nie meer beperk nie."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Skakel oor na werkapp?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Jou organisasie laat jou net toe om oproepe van werkapps af te maak"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Jou organisasie laat jou net toe om boodskappe van werkapps af te stuur"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Jy kan foonoproepe net van jou persoonlike Foon-app af maak. Oproepe wat met persoonlike Foon gemaak word, sal by jou persoonlike oproepgeskiedenis gevoeg word."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Jy kan SMS-boodskappe net van jou persoonlike Boodskappe-app af stuur."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Gebruik persoonlike blaaier"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Gebruik werkblaaier"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Bel"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Werk 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Toets"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Gemeenskaplik"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Werkprofiel"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privaat ruimte"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Kloon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Gemeenskaplik"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitiewe kennisgewinginhoud is versteek"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Appinhoud is weens sekuriteit van skermdeling verberg"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Stel Vingerafdrukslot weer op"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> het nie goed gewerk nie en is uitgevee. Stel dit weer op om jou foon met vingerafdruk te ontsluit."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> en <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> het nie goed gewerk nie en is uitgevee. Stel dit weer op om jou foon met jou vingerafdruk te ontsluit."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Stel Gesigslot weer op"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Jou gesigmodel het nie goed gewerk nie en is uitgevee. Stel dit weer op om jou foon met gesig te ontsluit."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Stel op"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Nie nou nie"</string> </resources> diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml index cf6cd30483e9..1d1ded6ce468 100644 --- a/core/res/res/values-am/strings.xml +++ b/core/res/res/values-am/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> ሲምዎን በመጠቀም ላይ ሳለ የመሣሪያዎን ልዩ መታወቂያ (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> ሲምዎን በመጠቀም ላይ ሳለ የመሣሪያዎን ልዩ መታወቂያ (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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"የተንቀሳቃሽ ስልክ አውታረ መረብ ደኅንነት ቅንብሮች"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"የበለጠ ለመረዳት"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ገባኝ"</string> <string name="fcComplete" msgid="1080909484660507044">" ኮድ ባህሪይ ተጠናቋል።"</string> <string name="fcError" msgid="5325116502080221346">"የተያያዥ ችግር ወይም ትክከል ያልሆነኮድ ባህሪ።"</string> <string name="httpErrorOk" msgid="6206751415788256357">"እሺ"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"የድምጽ እርዳታ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"መቆለፊያ"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"አዲስ ማሳወቂያ"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"አካላዊ ቁልፍ ሰሌዳ"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ደህንነት"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"የስልክ ጥሪዎችን ማድረግ የሚችሉት ከግል የስልክ መተግበሪያዎ ብቻ ነው። በግል ስልክ የተደረጉ ጥሪዎች ወደ የግል የጥሪ ታሪክዎ ይታከላሉ።"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"የኤስኤምኤስ መልዕክቶችን ከግል የመልዕክት መተግበሪያዎ ብቻ ነው መላክ የሚችሉት።"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"የግል አሳሽ ተጠቀም"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"የስራ አሳሽ ተጠቀም"</string> <string name="miniresolver_call" msgid="6386870060423480765">"ደውል"</string> @@ -2413,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"ሥራ 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ሙከራ"</string> <string name="profile_label_communal" msgid="8743921499944800427">"የጋራ"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"የሥራ መገለጫ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"የግል ቦታ"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"አባዛ"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"የጋራ"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"አደገኛ የማሳወቂያ ይዘት ተደብቋል"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ለደኅንነት ሲባል የመተግበሪያ ይዘት ከማያ ገጽ ማጋራት ተደብቋል"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ከሳተላይት ጋር በራስ-ሰር ተገናኝቷል"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"በጣት አሻራ መክፈቻን እንደገና ያዋቅሩ"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> በደንብ እየሠራ አልነበረም እና ተሰርዟል። ስልክዎን በጣት አሻራ ለመክፈት እንደገና ያዋቅሩት።"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> እና <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> በደንብ እየሠሩ አልነበረም እና ተሰርዘዋል። ስልክዎን በጣት አሻራ ለመክፈት እንደገና ያዋቅሯቸው።"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"በመልክ መክፈትን እንደገና ያዋቅሩ"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"የእርስዎ የመልክ ሞዴል በደንብ እየሠራ አልነበረም እና ተሰርዟል። ስልክዎን በመልክ ለመክፈት እንደገና ያዋቅሩት።"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"ያዋቅሩ"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"አሁን አይደለም"</string> </resources> diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml index dfaa28555cf8..6ee9d5dab3a3 100644 --- a/core/res/res/values-ar/strings.xml +++ b/core/res/res/values-ar/strings.xml @@ -157,31 +157,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"أمان شبكة الجوّال"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"التشفير والإشعارات للشبكات غير المشفرة"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"تم الوصول إلى رقم تعريف الجهاز"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"في <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>، رصدت إحدى الشبكات المجاورة المعرّف الفريد لجهازك (رقم التعريف الدولي للمشترك في خدمات الجوّال (IMSI) أو IMEI) أثناء استخدام شريحة SIM لشبكة \"<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>\""</string> + <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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"حسنًا"</string> <string name="fcComplete" msgid="1080909484660507044">"اكتمل رمز الميزة."</string> <string name="fcError" msgid="5325116502080221346">"حدثت مشكلة بالاتصال أو أن رمز الميزة غير صحيح."</string> <string name="httpErrorOk" msgid="6206751415788256357">"حسنًا"</string> @@ -299,6 +287,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"المساعد الصوتي"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"إلغاء التأمين"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"إشعار جديد"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"لوحة المفاتيح الخارجية"</string> <string name="notification_channel_security" msgid="8516754650348238057">"الأمان"</string> @@ -861,7 +851,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> @@ -1916,8 +1906,7 @@ <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"طلب إدخال رقم التعريف الشخصي قبل إزالة التثبيت"</string> <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"طلب إدخال نقش فتح القفل قبل إزالة التثبيت"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"طلب إدخال كلمة المرور قبل إزالة التثبيت"</string> - <!-- no translation found for package_installed_device_owner (8684974629306529138) --> - <skip /> + <string name="package_installed_device_owner" msgid="8684974629306529138">"تم التثبيت من قِبل المشرف.\nانتقِل إلى الإعدادات للاطّلاع على الأذونات الممنوحة"</string> <string name="package_updated_device_owner" msgid="7560272363805506941">"تم التحديث بواسطة المشرف"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"تم الحذف بواسطة المشرف"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"حسنًا"</string> @@ -2161,6 +2150,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> + <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> @@ -2235,10 +2226,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"يمكنك فقط إجراء مكالمات هاتفية من تطبيق الهاتف الشخصي، وستتم إضافة المكالمات التي أجريتها باستخدام هاتفك الشخصي إلى سجل المكالمات الشخصي."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"يمكنك فقط إرسال رسائل قصيرة SMS من تطبيق \"الرسائل\" الشخصي."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"استخدام المتصفّح الشخصي"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"استخدام متصفّح العمل"</string> <string name="miniresolver_call" msgid="6386870060423480765">"الاتصال"</string> @@ -2411,13 +2400,17 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"تم ضبط تنسيق لوحة المفاتيح على <xliff:g id="LAYOUT_1">%1$s</xliff:g> و<xliff:g id="LAYOUT_2">%2$s</xliff:g> و<xliff:g id="LAYOUT_3">%3$s</xliff:g>… انقر لتغييره."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"تم إعداد لوحات المفاتيح الخارجية"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"انقر لعرض لوحات المفاتيح."</string> - <string name="profile_label_private" msgid="6463418670715290696">"ملف شخصي خاص"</string> + <string name="profile_label_private" msgid="6463418670715290696">"المساحة الخاصة"</string> <string name="profile_label_clone" msgid="769106052210954285">"نسخة طبق الأصل"</string> - <string name="profile_label_work" msgid="3495359133038584618">"ملف العمل"</string> + <string name="profile_label_work" msgid="3495359133038584618">"مساحة العمل"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"ملف العمل 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"ملف العمل 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ملف شخصي تجريبي"</string> <string name="profile_label_communal" msgid="8743921499944800427">"ملف شخصي مشترك"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ملف العمل"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"المساحة الخاصة"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"نسخة طبق الأصل"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ملف شخصي مشترك"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"تم إخفاء المحتوى الحساس في الإشعار"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"تم إخفاء محتوى التطبيق بعد تفعيل ميزة \"مشاركة الشاشة\" للحفاظ على أمانك"</string> @@ -2426,4 +2419,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"إعادة إعداد ميزة \"فتح الجهاز ببصمة الإصبع\""</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"هناك مشكلة في <xliff:g id="FINGERPRINT">%s</xliff:g> وتم حذفها. يُرجى إعدادها مرة أخرى لفتح قفل هاتفك باستخدام بصمة الإصبع."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"هناك مشكلة في <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> و<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> وتم حذفهما. يُرجى إعادة إعدادهما لفتح قفل هاتفك باستخدام بصمة الإصبع."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"إعادة إعداد ميزة \"فتح الجهاز بالتعرّف على الوجه\""</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"هناك مشكلة في نموذج الوجه الخاص بك وتم حذفه. يُرجى إعداده مرة أخرى لفتح قفل هاتفك باستخدام وجهك."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"إعداد"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"لاحقًا"</string> </resources> diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml index c695e86a9b19..9064df1d5ae5 100644 --- a/core/res/res/values-as/strings.xml +++ b/core/res/res/values-as/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> ছিম ব্যৱহাৰ কৰাৰ সময়ত আপোনাৰ ডিভাইচৰ অদ্বিতীয় আইডি (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> ছিম ব্যৱহাৰ কৰাৰ সময়ত আপোনাৰ ডিভাইচৰ অদ্বিতীয় আইডি (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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ম’বাইল নেটৱৰ্কৰ সুৰক্ষাৰ ছেটিং"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"অধিক জানক"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"বুজি পালোঁ"</string> <string name="fcComplete" msgid="1080909484660507044">"সুবিধাৰ ক\'ড সম্পূর্ণ হ’ল।"</string> <string name="fcError" msgid="5325116502080221346">"সংযোগত সমস্যা হৈছে বা সুবিধাৰ ক\'ড অমান্য।"</string> <string name="httpErrorOk" msgid="6206751415788256357">"ঠিক আছে"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"কণ্ঠধ্বনিৰে সহায়"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"লকডাউন"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"৯৯৯+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"নতুন জাননী"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"কায়িক কীব’ৰ্ড"</string> <string name="notification_channel_security" msgid="8516754650348238057">"সুৰক্ষা"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"আপুনি আপোনাৰ ব্যক্তিগত Phone এপৰ পৰা কেৱল ফ’ন কল কৰিব পাৰিব। ব্যক্তিগত Phone এপৰ জৰিয়তে কৰা কল আপোনাৰ ব্যক্তিগত কলৰ ইতিহাসত যোগ দিয়া হ’ব।"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"আপুনি আপোনাৰ ব্যক্তিগত Messages এপৰ পৰা কেৱল এছএমএছ বাৰ্তা পঠিয়াব পাৰিব।"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ব্যক্তিগত ব্ৰাউজাৰ ব্যৱহাৰ কৰক"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"কৰ্মস্থানৰ ব্ৰাউজাৰ ব্যৱহাৰ কৰক"</string> <string name="miniresolver_call" msgid="6386870060423480765">"কল কৰক"</string> @@ -2413,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"কৰ্মস্থান ৩"</string> <string name="profile_label_test" msgid="9168641926186071947">"পৰীক্ষা"</string> <string name="profile_label_communal" msgid="8743921499944800427">"শ্বেয়াৰ কৰা"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"কৰ্মস্থানৰ প্ৰ’ফাইল"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"প্ৰাইভেট স্পে’চ"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ক্ল’ন"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"সম্প্ৰদায়ৰ সৈতে জড়িত"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"সংবেদনশীল জাননী লুকুওৱা হৈছে"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"সুৰক্ষাৰ বাবে এপৰ সমল স্ক্ৰীণ শ্বেয়াৰ কৰাৰ পৰা লুকুৱাই ৰখা হৈছে"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"উপগ্ৰহৰ সৈতে স্বয়ংক্ৰিয়ভাৱে সংযুক্ত হৈছে"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ফিংগাৰপ্ৰিণ্ট আনলক পুনৰ ছেট আপ কৰক"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g>এ ভালদৰে কাম কৰা নাছিল আৰু সেইটো মচি পেলোৱা হৈছে। ফিংগাৰপ্ৰিণ্টৰ জৰিয়তে আপোনাৰ ফ’নটো আনলক কৰিবলৈ এইটো পুনৰ ছেট আপ কৰক।"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> আৰু <xliff:g id="FINGERPRINT_1">%2$s</xliff:g>এ ভালদৰে কাম কৰা নাছিল আৰু সেয়া মচি পেলোৱা হৈছে। ফিংগাৰপ্ৰিণ্টৰ জৰিয়তে আপোনাৰ ফ’নটো আনলক কৰিবলৈ সেয়া পুনৰ ছেট আপ কৰক।"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ফে’চ আনলক পুনৰ ছেট আপ কৰক"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"আপোনাৰ মুখাৱয়বৰ মডেলটোৱে ভালদৰে কাম কৰা নাছিল আৰু সেইটো মচি পেলোৱা হৈছে। মুখাৱয়বৰ জৰিয়তে আপোনাৰ ফ’নটো আনলক কৰিবলৈ এইটো পুনৰ ছেট আপ কৰক।"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"ছেট আপ কৰক"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"এতিয়া নহয়"</string> </resources> diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml index c110e3e3b419..23a96689c073 100644 --- a/core/res/res/values-az/strings.xml +++ b/core/res/res/values-az/strings.xml @@ -153,31 +153,19 @@ <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> saniyə sonra"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönləndirilmədi"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönləndirilmədi"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobil şəbəkə güvənliyi"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Şifrələmə, şifrələnməmiş şəbəkələr üçün bildirişlər"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Cihaz ID-nə giriş edildi"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> radələrində <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM-i istifadə edərkən yaxınlıqdakı şəbəkə cihazın unikal ID məlumatını (IMSI və ya IMEI) qeydə alıb"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> radələrində <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM-i istifadə edərkən yaxınlıqdakı şəbəkə cihazın unikal ID məlumatını (IMSI və ya IMEI) qeydə alıb.\n\nBu o deməkdir ki, məkan, fəaliyyət və ya kimliyiniz qeydə alınıb. Bu, adi haldır, lakin məxfilik barədə narahat olan insanlar üçün problem ola bilər."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> adlı şifrələnmiş şəbəkəyə qoşulub"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM bağlantısı artıq daha güvənlidir"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Şifrələnməmiş şəbəkəyə qoşulub"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Hazırda <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-dən istifadə edərkən zəng, mesaj və data güvənliyi daha aşağıdır"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Hazırda <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-dən istifadə edərkən zəng, mesaj və data güvənliyi daha aşağıdır.\n\nBağlantı yenidən şifrələndikdə başqa bildiriş alacaqsınız."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobil şəbəkə güvənliyi ayarları"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Ətraflı məlumat"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Anladım"</string> <string name="fcComplete" msgid="1080909484660507044">"Özəllik kodu tamamlandı."</string> <string name="fcError" msgid="5325116502080221346">"Əlaqə problemi və ya yanlış funksiya kodu."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Səs Yardımçısı"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Kilidləyin"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Yeni bildiriş"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziki klaviatura"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Güvənlik"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ekran şəkli çəkin"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Ekran şəkli çəkilə bilər."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Önizləmə, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"kənarlaşdırın"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"status panelini deaktivləşdir və ya dəyişdir"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Tətbiqə status panelini deaktiv etməyə və ya sistem ikonalarını əlavə etmək və ya silmək imkanı verir."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"status paneli edin"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rejim üçün məlumat bildirişi"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Enerjiyə Qənaət aktivdir"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Batareyanın ömrünü uzatmaq üçün batareyadan istifadəni azaldın"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Enerjiyə Qənaət yanılıdır"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batareya istifadəsini artırmaq üçün Enerjiyə Qənaət yandırıldı"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Enerjiyə qənaət"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Enerjiyə qənaət deaktivdir"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonun kifayət qədər enerjisi var. Funksiyalar artıq məhdud deyil."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"İş tətbiqinə dəyişilsin?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Təşkilat yalnız iş tətbiqindən zəng etməyə icazə verir"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Təşkilat yalnız iş tətbiqindən mesaj göndərməyə icazə verir"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Yalnız şəxsi Telefon tətbiqindən telefon zəngləri edə bilərsiniz. Şəxsi Telefon ilə edilmiş zənglər şəxsi zəng tarixçənizə əlavə ediləcək."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Yalnız şəxsi Mesajlaşma tətbiqindən SMS mesajları göndərə bilərsiniz."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Şəxsi brauzerdən istifadə edin"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"İş brauzerindən istifadə edin"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Zəng edin"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"İş 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Kommunal"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"İş profili"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Məxfi sahə"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Kommunal"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Həssas bildiriş kontenti gizlədildi"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Güvənlik üçün tətbiq kontenti ekran paylaşımından gizlədildi"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Barmaqla Kilidaçmanı yenidən ayarlayın"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> yaxşı işləmirdi və silindi. Telefonu barmaq izi ilə kiliddən çıxarmaq üçün onu yenidən ayarlayın."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> və <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> yaxşı işləmirdi və silindi. Telefonu barmaq izi ilə kiliddən çıxarmaq üçün onları yenidən ayarlayın."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Üzlə Kilidaçmanı yenidən ayarlayın"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Üz modeliniz yaxşı işləmirdi və silindi. Telefonu üzlə kiliddən çıxarmaq üçün onu yenidən ayarlayın."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Ayarlayın"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"İndi yox"</string> </resources> diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml index 2977bfbab953..db9a93f7a63a 100644 --- a/core/res/res/values-b+sr+Latn/strings.xml +++ b/core/res/res/values-b+sr+Latn/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunde/i"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije prosleđeno"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Bezbednost na mobilnoj mreži"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Šifrovanje, obaveštenja za nešifrovane mreže"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Pristupljeno je ID-u uređaja"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"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"</string> + <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 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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saznajte više"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Važi"</string> <string name="fcComplete" msgid="1080909484660507044">"Kôd funkcije je izvršen."</string> <string name="fcError" msgid="5325116502080221346">"Problemi sa vezom ili nevažeći kôd funkcije."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Potvrdi"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Glasovna pomoć"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zaključavanje"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Novo obaveštenje"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tastatura"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Bezbednost"</string> @@ -339,7 +329,7 @@ <string name="permgroupdesc_storage" msgid="5378659041354582769">"pristup fajlovima na uređaju"</string> <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Muzika i zvuk"</string> <string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"pristup muzici i audio sadržaju na uređaju"</string> - <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Slike i video snimci"</string> + <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Slike i videi"</string> <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"pristup slikama i videima na uređaju"</string> <string name="permgrouplab_microphone" msgid="2480597427667420076">"Mikrofon"</string> <string name="permgroupdesc_microphone" msgid="1047786732792487722">"snima zvuk"</string> @@ -847,11 +837,11 @@ <string name="policylab_watchLogin" msgid="7599669460083719504">"Nadzor pokušaja otključavanja ekrana"</string> <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Prati broj netačno unetih lozinki prilikom otključavanja ekrana i zaključava tablet ili briše podatke sa tableta ako je netačna lozinka uneta previše puta."</string> <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava Android TV uređaj ili briše sve podatke sa Android TV uređaja ako se unese previše netačnih lozinki."</string> - <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Prati broj netačno unetih lozinki pri otključavanju ekrana i zaključava sistem za info-zabavu ili briše sve podatke sa sistema za info-zabavu ako je netačna lozinka uneta previše puta."</string> + <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Prati broj netačno unetih lozinki pri otključavanju ekrana i zaključava sistem za informacije i zabavu ili briše sve podatke sa sistema za informacije i zabavu ako je netačna lozinka uneta previše puta."</string> <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Prati broj netačno unetih lozinki pri otključavanju ekrana i zaključava telefon ili briše sve podatke sa telefona ako je netačna lozinka uneta previše puta."</string> <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava tablet ili briše sve podatke ovog korisnika ako se unese previše netačnih lozinki."</string> <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava Android TV uređaj ili briše sve podatke ovog korisnika ako se unese previše netačnih lozinki."</string> - <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava sistem za info-zabavu ili briše sve podatke ovog profila ako se unese previše netačnih lozinki."</string> + <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava sistem za informacije i zabavu ili briše sve podatke ovog profila ako se unese previše netačnih lozinki."</string> <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Nadgleda broj netačnih lozinki unetih pri otključavanju ekrana i zaključava telefon ili briše sve podatke ovog korisnika ako se unese previše netačnih lozinki."</string> <string name="policylab_resetPassword" msgid="214556238645096520">"Promena zaključavanja ekrana"</string> <string name="policydesc_resetPassword" msgid="4626419138439341851">"Menja otključavanje ekrana."</string> @@ -860,13 +850,13 @@ <string name="policylab_wipeData" msgid="1359485247727537311">"Brisanje svih podataka"</string> <string name="policydesc_wipeData" product="tablet" msgid="7245372676261947507">"Brisanje podataka na tabletu bez upozorenja resetovanjem na fabrička podešavanja."</string> <string name="policydesc_wipeData" product="tv" msgid="513862488950801261">"Briše podatke Android TV uređaja bez upozorenja pomoću resetovanja na fabrička podešavanja."</string> - <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Briše podatke na sistemu za info-zabavu bez upozorenja resetovanjem na fabrička podešavanja."</string> + <string name="policydesc_wipeData" product="automotive" msgid="660804547737323300">"Briše podatke na sistemu za informacije i zabavu bez upozorenja resetovanjem na fabrička podešavanja."</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Brisanje podataka na telefonu bez upozorenja resetovanjem na fabrička podešavanja."</string> <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Brisanje podataka profila"</string> <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Obriši podatke korisnika"</string> <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Briše podatke ovog korisnika na ovom tabletu bez upozorenja."</string> <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Briše podatke ovog korisnika na ovom Android TV uređaju bez upozorenja."</string> - <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Briše podatke ovog profila na ovom sistemu za info-zabavu bez upozorenja."</string> + <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Briše podatke ovog profila na ovom sistemu za informacije i zabavu bez upozorenja."</string> <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Briše podatke ovog korisnika na ovom telefonu bez upozorenja."</string> <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Podesite globalni proksi server uređaja"</string> <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Podešava globalni proksi uređaja koji će se koristiti dok su smernice omogućene. Samo vlasnik uređaja može da podesi globalni proksi."</string> @@ -2157,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Obaveštenje o informacijama Rutinskog režima"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ušteda baterije je uključena"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Smanjuje se potrošnja baterije da bi se produžilo njeno trajanje"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Ušteda baterije je uključena"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Ušteda baterije je uključena da bi se produžilo trajanje baterije"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ušteda baterije"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ušteda baterije je isključena"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija telefona je dovoljno napunjena. Funkcije više nisu ograničene."</string> @@ -2231,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Želite da prebacite na poslovnu aplikaciju?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Vaša organizacija dozvoljava pozivanje samo iz poslovnih aplikacija"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Vaša organizacija dozvoljava slanje poruka samo iz poslovnih aplikacija"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Telefonske pozive možete da upućujete samo iz lične aplikacije Telefon. Pozivi upućeni pomoću lične aplikacije Telefon dodaju se u ličnu istoriju poziva."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS poruke možete da šaljete samo iz lične aplikacije Messages."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Koristi lični pregledač"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Koristi poslovni pregledač"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Pozovi"</string> @@ -2414,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Posao 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Zajedničko"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Poslovni profil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privatan prostor"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klonirano"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Zajedničko"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Osetljiv sadržaj obaveštenja je skriven"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sadržaj aplikacije je skriven za deljenje sadržaja ekrana zbog bezbednosti"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatski povezano sa satelitom"</string> @@ -2423,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ponovo podesite otključavanje otiskom prsta"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nije funkcionisao i izbrisali smo ga. Ponovo ga podesite da biste telefon otključavali otiskom prsta."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu funkcionisali i izbrisali smo ih. Ponovo ih podesite da biste telefon otključavali otiskom prsta."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Ponovo podesite otključavanje licem"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Vaš model lica nije funkcionisao i izbrisali smo ga. Ponovo ga podesite da biste telefon otključavali licem."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Podesi"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ne sada"</string> </resources> diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml index 0a009a2ab2db..ec19c2da015e 100644 --- a/core/res/res/values-be/strings.xml +++ b/core/res/res/values-be/strings.xml @@ -155,31 +155,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Сеткавая бяспека"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шыфраванне, апавяшчэнні для незашыфраваных сетак"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Атрыманы доступ да ідэнтыфікатара прылады"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"У <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> у сетцы паблізу быў запісаны ўнікальны ідэнтыфікатар вашай прылады (IMSI або IMEI) пры выкарыстанні SIM-карты <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ОК"</string> <string name="fcComplete" msgid="1080909484660507044">"Код аб\'екта завершаны."</string> <string name="fcError" msgid="5325116502080221346">"Праблема падлучэння ці няправільны код функцыі."</string> <string name="httpErrorOk" msgid="6206751415788256357">"ОК"</string> @@ -297,6 +285,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Галас. дапамога"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Блакіроўка"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Новае апавяшчэнне"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Фізічная клавіятура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Бяспека"</string> @@ -373,8 +363,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Зрабіць здымак экрана"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Можна зрабіць здымак экрана."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Перадпрагляд, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"закрыць"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"адключаць ці змяняць радок стану"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Дазваляе прыкладанням адключаць радок стану або дадаваць і выдаляць сістэмныя значкі."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"быць панэллю стану"</string> @@ -2159,6 +2148,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> + <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> @@ -2233,10 +2224,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Вы можаце ажыццяўляць выклікі толькі з асабістай праграмы \"Тэлефон\". Зробленыя выклікі будуць дададзены ў гісторыю выклікаў."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Вы можаце адпраўляць SMS-паведамленні толькі з асабістай праграмы \"Паведамленні\"."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Скарыстаць асабісты браўзер"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Скарыстаць працоўны браўзер"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Выклікаць"</string> @@ -2416,8 +2405,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Працоўны 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Тэставы"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Супольны"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Працоўны профіль"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Прыватная прастора"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Супольны"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Канфідэнцыяльнае змесціва ў апавяшчэннях схавана"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Змесціва праграмы выключана з абагульвання экрана ў мэтах бяспекі"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Аўтаматычна падключана да сістэм спадарожнікавай сувязі"</string> @@ -2425,4 +2417,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Наладзіць разблакіроўку адбіткам пальца паўторна"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Адбітак пальца \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" не працаваў належным чынам і быў выдалены. Каб мець магчымасць разблакіраваць тэлефон з дапамогай адбітка пальца, наладзьце яго яшчэ раз."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Адбіткі пальцаў \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" і \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" не працавалі належным чынам і былі выдалены. Каб мець магчымасць разблакіраваць тэлефон з дапамогай адбітка пальца, наладзьце іх яшчэ раз."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Паўторна наладзьце распазнаванне твару"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Мадэль твару не працавала належным чынам і была выдалена. Каб мець магчымасць разблакіраваць тэлефон з дапамогай распазнавання твару, наладзьце яго яшчэ раз."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Наладзіць"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Не зараз"</string> </resources> diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml index 134881dc396e..1b73710c4548 100644 --- a/core/res/res/values-bg/strings.xml +++ b/core/res/res/values-bg/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> близка до вас мрежа записа уникалния идентификатор на устройството ви (IMSI или IMEI), докато използвахте SIM картата си от <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), докато използвахте 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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Разбрах"</string> <string name="fcComplete" msgid="1080909484660507044">"Кодът за функцията се изпълни."</string> <string name="fcError" msgid="5325116502080221346">"Има проблем с връзката или кодът за функцията е невалиден."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Гласова помощ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Заключване"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Ново известие"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физическа клавиатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Сигурност"</string> @@ -1738,7 +1728,7 @@ <string name="accessibility_shortcut_off" msgid="3651336255403648739">"Без включване"</string> <string name="accessibility_shortcut_menu_item_status_on" msgid="6608392117189732543">"ВКЛ."</string> <string name="accessibility_shortcut_menu_item_status_off" msgid="5531598275559472393">"ИЗКЛ."</string> - <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Искате ли да разрешите на <xliff:g id="SERVICE">%1$s</xliff:g> да има пълен контрол над устройството ви?"</string> + <string name="accessibility_enable_service_title" msgid="3931558336268541484">"Искате ли да разрешите на „<xliff:g id="SERVICE">%1$s</xliff:g>“ да има пълен контрол над устройството ви?"</string> <string name="accessibility_service_warning_description" msgid="291674995220940133">"Пълният контрол е подходящ за приложенията, които помагат на потребителите със специални нужди, но не и за повечето приложения."</string> <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Преглед и управление на екрана"</string> <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Услугата може да чете цялото съдържание на екрана и да показва такова върху други приложения."</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Можете да извършвате телефонни обаждания само от приложението Телефон в личния си потребителски профил. Обажданията, които извършите с това приложение, ще бъдат добавени към личната ви история на обажданията."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Можете да изпращате SMS съобщения само от приложението Messages в личния си потребителски профил."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Използване на личния браузър"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Използване на служебния браузър"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Обаждане"</string> @@ -2413,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Служебни 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Тестване"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Общи"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Служебен потребителски профил"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Частно пространство"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клониране"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Общи"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Деликатното съдържание в известието е скрито"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Съдържанието на приложението е скрито от функцията за споделяне на екрана от съображения за сигурност"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Повторно настройване на „Отключване с отпечатък“"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Отпечатъкът „<xliff:g id="FINGERPRINT">%s</xliff:g>“ бе изтрит, защото не работеше добре. Настройте го отново, за да отключвате телефона си с отпечатък."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Отпечатъците „<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>“ и „<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>“ бяха изтрити, защото не работеха добре. Настройте ги отново, за да отключвате телефона си с отпечатък."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Повторно настройване на „Отключване с лице“"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Моделът на лицето ви бе изтрит, защото не работеше добре. Настройте го отново, за да отключвате телефона си с лице."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Настройване"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Не сега"</string> </resources> diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml index 866f71fe4d4c..0e32bbe64c86 100644 --- a/core/res/res/values-bn/strings.xml +++ b/core/res/res/values-bn/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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_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="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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"আরও জানুন"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"বুঝেছি"</string> <string name="fcComplete" msgid="1080909484660507044">"বৈশিষ্ট্যসূচক কোড সম্পূর্ণ হয়েছে৷"</string> <string name="fcError" msgid="5325116502080221346">"সংযোগ সমস্যা বা অবৈধ বৈশিষ্ট্যসূচক কোড৷"</string> <string name="httpErrorOk" msgid="6206751415788256357">"ঠিক আছে"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ভয়েস সহায়তা"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"লকডাউন"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"৯৯৯+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"নতুন বিজ্ঞপ্তি"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ফিজিক্যাল কীবোর্ড"</string> <string name="notification_channel_security" msgid="8516754650348238057">"নিরাপত্তা"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"স্ক্রিনশট নিন"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"ডিসপ্লের একটি স্ক্রিনশট নিতে পারেন।"</string> <string name="dream_preview_title" msgid="5570751491996100804">"প্রিভিউ, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"বাতিল করুন"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"স্ট্যাটাস বার নিষ্ক্রিয় অথবা সংশোধন করে"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"অ্যাপ্লিকেশনকে স্ট্যাটাস বার অক্ষম করতে এবং সিস্টেম আইকনগুলি সরাতে দেয়৷"</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"স্থিতি দন্ডে থাকুন"</string> @@ -657,7 +646,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> @@ -1224,7 +1213,7 @@ <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{{max}টির মধ্যে একটি স্টার}one{#টির মধ্যে {max}টি স্টার}other{#টির মধ্যে {max}টি স্টার}}"</string> <string name="in_progress" msgid="2149208189184319441">"কাজ চলছে"</string> <string name="whichApplication" msgid="5432266899591255759">"এটি ব্যবহার করে ক্রিয়াকলাপ সম্পূর্ণ করুন"</string> - <string name="whichApplicationNamed" msgid="6969946041713975681">"%1$s ব্যবহার করে ক্রিয়াকলাপ সম্পূর্ণ করুন"</string> + <string name="whichApplicationNamed" msgid="6969946041713975681">"%1$s ব্যবহার করে কাজটি করুন"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"ক্রিয়াকলাপ সম্পূর্ণ করুন"</string> <string name="whichViewApplication" msgid="5733194231473132945">"এর মাধ্যমে খুলুন"</string> <string name="whichViewApplicationNamed" msgid="415164730629690105">"%1$s দিয়ে খুলুন"</string> @@ -1250,7 +1239,7 @@ <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"%1$s দিয়ে ছবি তুলুন"</string> <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"ছবি তুলুন"</string> <string name="alwaysUse" msgid="3153558199076112903">"এই ক্রিয়াটির জন্য এটিকে ডিফল্টরুপে ব্যবহার করুন৷"</string> - <string name="use_a_different_app" msgid="4987790276170972776">"আলাদা কোনো অ্যাপ্লিকেশান ব্যবহার করুন"</string> + <string name="use_a_different_app" msgid="4987790276170972776">"আলাদা কোনও অ্যাপ ব্যবহার করুন"</string> <string name="clearDefaultHintMsg" msgid="1325866337702524936">"সিস্টেম সেটিংস > অ্যাপ্স > ডাউনলোড করাগুলি এ গিয়ে ডিফল্ট সরিয়ে দিন৷"</string> <string name="chooseActivity" msgid="8563390197659779956">"একটি ক্রিয়া বেছে নিন"</string> <string name="chooseUsbActivity" msgid="2096269989990986612">"USB ডিভাইসটির জন্য একটি অ্যাপ্লিকেশান বেছে নিন"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"আপনার ব্যক্তিগত ফোন অ্যাপ থেকে শুধুমাত্র ফোন কল করতে পারবেন। ব্যক্তিগত ফোন থেকে করা কল আপনার ব্যক্তিগত কলের ইতিহাসে যোগ করা হবে।"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"আপনার ব্যক্তিগত Messages অ্যাপ থেকে শুধুমাত্র এসএমএস মেসেজ পাঠাতে পারবেন।"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ব্যক্তিগত ব্রাউজার ব্যবহার করুন"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"অফিস ব্রাউজার ব্যবহার করুন"</string> <string name="miniresolver_call" msgid="6386870060423480765">"কল করুন"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"৩য় অফিস"</string> <string name="profile_label_test" msgid="9168641926186071947">"পরীক্ষা"</string> <string name="profile_label_communal" msgid="8743921499944800427">"কমিউনাল"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"অফিস প্রোফাইল"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"প্রাইভেট স্পেস"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ক্লোন"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"কমিউনাল"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"সংবেদনশীল বিজ্ঞপ্তির কন্টেন্ট লুকানো আছে"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"নিরাপত্তার জন্য স্ক্রিন শেয়ার করা থেকে লুকানো অ্যাপের কন্টেন্ট"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"স্যাটেলাইটের সাথে অটোমেটিক কানেক্ট করা হয়েছে"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"\'ফিঙ্গারপ্রিন্ট আনলক\' আবার সেট-আপ করুন"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ভালোভাবে কাজ করছিল না বলে সেটি মুছে ফেলা হয়েছে। ফিঙ্গারপ্রিন্ট ব্যবহার করে আপনার ফোন আনলক করতে হলে এটি আবার সেট-আপ করুন।"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ও <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ভালোভাবে কাজ করছিল না বলে মুছে ফেলা হয়েছে। ফিঙ্গারপ্রিন্ট ব্যবহার করে আপনার ফোন আনলক করতে হলে সেগুলি আবার সেট-আপ করুন।"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"\'ফেস আনলক\' আবার সেট-আপ করুন"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"আপনার ফেস মডেল ভালোভাবে কাজ করছিল না বলে সেটি মুছে ফেলা হয়েছে। ফেস ব্যবহার করে আপনার ফোন আনলক করতে হলে এটি আবার সেট-আপ করুন।"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"সেট-আপ করুন"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"এখন নয়"</string> </resources> diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml index d641d5a7b834..ddc515325992 100644 --- a/core/res/res/values-bs/strings.xml +++ b/core/res/res/values-bs/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> za <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundi"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđen"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Poziv nije proslijeđen"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sigurnost mobilne mreže"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Šifriranje, obavještenja za nešifrirane mreže"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Pristupljeno je ID-u uređaja"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"U <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> mreža u blizini je zabilježila jedinstveni ID uređaja (IMSI ili IMEI) koristeći SIM mreže <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"U <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> mreža u blizini je zabilježila jedinstveni ID uređaja (IMSI ili IMEI) koristeći SIM mreže <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nOvo znači da su vaša lokacija, aktivnost ili identitet zabilježeni. Ovo je uobičajena praksa, ali može predstavljati problem za osobe zabrinute za privatnost."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Povezani ste sa šifriranom mrežom <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Povezivanje SIM-a s mrežom <xliff:g id="NETWORK_NAME">%1$s</xliff:g> je sada sigurnije"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Povezani ste s nešifriranom mrežom"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pozivi, poruke i podaci su trenutno osjetljiviji dok koristite SIM mreže <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pozivi, poruke i podaci su trenutno osjetljiviji dok koristite SIM mreže <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKada veza ponovo bude šifrirana, primit ćete još jedno obavještenje."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Postavke sigurnosti mobilne mreže"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saznajte više"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Razumijem"</string> <string name="fcComplete" msgid="1080909484660507044">"Kôd za posebne usluge potpun."</string> <string name="fcError" msgid="5325116502080221346">"Problem sa povezivanjem ili nevažeći kôd za posebne usluge."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Uredu"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Glasovna pomoć"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zaključaj"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Novo obavještenje"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tastatura"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sigurnost"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"praviti snimke ekrana"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Može napraviti snimak ekrana."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Pregled, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"odbacivanje"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"onemogućavanje ili mijenjanje statusne trake"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Dozvoljava aplikaciji onemogućavanje statusne trake ili dodavanje i uklanjanje sistemskih ikona."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"funkcioniranje u vidu statusne trake"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Obavještenje za informacije Rutinskog načina"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ušteda baterije je uključena"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Smanjena je potrošnja baterije da se produži vijek trajanja baterije"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Ušteda baterije je uključena"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Ušteda baterije je uključena radi produženja vijeka trajanja baterije"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ušteda baterije"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ušteda baterije je isključena"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon se dovoljno napunio. Funkcije nisu više ograničene."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Prebaciti na poslovnu aplikaciju?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Vaša organizacija vam dozvoljava da upućujete pozive samo iz poslovnih aplikacija"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Vaša organizacija vam dozvoljava da šaljete poruke samo iz poslovnih aplikacija"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Telefonske pozive možete upućivati samo iz lične aplikacije Telefon. Pozivi upućeni ličnim telefonom će se dodavati u vašu ličnu historiju poziva."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS poruke možete slati samo iz lične aplikacije Messages."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Koristi lični preglednik"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Koristi poslovni preglednik"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Pozovi"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"3. poslovno"</string> <string name="profile_label_test" msgid="9168641926186071947">"Testno"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Opće"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Radni profil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privatni prostor"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Opće"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Sakriven je osjetljiv sadržaj obavještenja"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sadržaj aplikacije je sakriven od dijeljenja ekrana radi sigurnosti"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatski je povezano sa satelitom"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ponovo postavite otključavanje otiskom prsta"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Otisak prsta <xliff:g id="FINGERPRINT">%s</xliff:g> nije dobro funkcionirao, pa je izbrisan. Postavite ga ponovo da otključavate telefon otiskom prsta."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Otisci prstiju <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu dobro funkcionirali, pa su izbrisani. Postavite ih ponovo da otključavate telefon otiskom prsta."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Ponovo postavite otključavanje licem"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Model lica nije dobro funkcionirao, pa je izbrisan. Postavite ga ponovo da otključavate telefon licem."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Postavite"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ne sada"</string> </resources> diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml index 79afa25e58c3..1fd814d3561a 100644 --- a/core/res/res/values-ca/strings.xml +++ b/core/res/res/values-ca/strings.xml @@ -85,7 +85,7 @@ <string name="NetworkPreferenceSwitchTitle" msgid="1008329951315753038">"No es pot accedir a la xarxa mòbil"</string> <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"Prova de canviar de xarxa preferent. Toca per canviar-la."</string> <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"Les trucades d\'emergència no estan disponibles"</string> - <string name="EmergencyCallWarningSummary" msgid="9102799172089265268">"Per poder fer trucades d\'emergència, cal tenir connexió a una xarxa mòbil"</string> + <string name="EmergencyCallWarningSummary" msgid="9102799172089265268">"Per poder fer trucades d\'emergència, cal tenir connexió de xarxa mòbil"</string> <string name="notification_channel_network_alert" msgid="4788053066033851841">"Alertes"</string> <string name="notification_channel_call_forward" msgid="8230490317314272406">"Desviació de trucades"</string> <string name="notification_channel_emergency_callback" msgid="54074839059123159">"Mode de devolució de trucada d\'emergència"</string> @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> després de <xliff:g id="TIME_DELAY">{2}</xliff:g> segons"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no s\'ha desviat"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no s\'ha desviat"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Seguretat de xarxes mòbils"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encriptació, notificacions de xarxes sense encriptació"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"S\'ha accedit a l\'identificador de dispositiu"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"A les <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una xarxa propera ha registrat l\'identificador únic del teu dispositiu (IMSI or IMEI) mentre s\'utilitzava la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A les <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, una xarxa propera ha registrat l\'identificador únic del teu dispositiu (IMSI or IMEI) mentre s\'utilitzava la SIM de <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nAixò vol dir que s\'han registrat la teva ubicació, activitat o identitat. Tot i ser una pràctica habitual, pot suposar un problema per a aquelles persones preocupades per la privadesa."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"T\'has connectat a la xarxa encriptada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connexió SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ara és més segura"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"T\'has connectat a una xarxa sense encriptació"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Les trucades, els missatges i les dades ara són més vulnerables amb la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Les trucades, els missatges i les dades ara són més vulnerables amb la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nRebràs una altra notificació quan la connexió torni a estar encriptada."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configuració de seguretat de xarxes mòbils"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Més informació"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entesos"</string> <string name="fcComplete" msgid="1080909484660507044">"Codi de funció completat."</string> <string name="fcError" msgid="5325116502080221346">"Problema de connexió o codi de funció no vàlid."</string> <string name="httpErrorOk" msgid="6206751415788256357">"D\'acord"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Assist. per veu"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueig de seguretat"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"+999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificació nova"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclat físic"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Seguretat"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fer una captura de pantalla"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pot fer una captura de la pantalla."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Previsualitza, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"ignora"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar o modificar la barra d\'estat"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permet que l\'aplicació desactivi la barra d\'estat o afegeixi i elimini icones del sistema."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"aparèixer a la barra d\'estat"</string> @@ -1918,8 +1907,8 @@ <string name="package_updated_device_owner" msgid="7560272363805506941">"Actualitzat per l\'administrador"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"Suprimit per l\'administrador"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"D\'acord"</string> - <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions a la xarxa."</string> - <string name="battery_saver_description" msgid="8518809702138617167">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions a la xarxa."</string> + <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions de xarxa."</string> + <string name="battery_saver_description" msgid="8518809702138617167">"Estalvi de bateria activa el tema fosc i limita o desactiva l\'activitat en segon pla, alguns efectes visuals, determinades funcions i algunes connexions de xarxa."</string> <string name="data_saver_description" msgid="4995164271550590517">"Per reduir l\'ús de dades, la funció Estalvi de dades evita que determinades aplicacions enviïn o rebin dades en segon pla. L\'aplicació que estiguis fent servir podrà accedir a les dades, però menys sovint. Això vol dir, per exemple, que les imatges no es mostraran fins que no les toquis."</string> <string name="data_saver_enable_title" msgid="7080620065745260137">"Vols activar l\'Estalvi de dades?"</string> <string name="data_saver_enable_button" msgid="4399405762586419726">"Activa"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificació d\'informació del mode de rutina"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Estalvi de bateria s\'ha activat"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"S\'està reduint l\'ús de la bateria per allargar-ne la durada"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"La funció Estalvi de bateria està activada"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"S\'ha activat la funció Estalvi de bateria per allargar la durada de la bateria"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Estalvi de bateria"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"L\'estalvi de bateria s\'ha desactivat"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El telèfon té prou bateria. Les funcions ja no estan restringides."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Vols canviar a l\'aplicació de treball?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"La teva organització només et permet fer trucades des d\'aplicacions de treball"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"La teva organització només et permet enviar missatges des d\'aplicacions de treball"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Només pots fer trucades des de la teva aplicació Telèfon personal. Les trucades que facis amb aquesta aplicació s\'afegiran al teu historial de trucades personal."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Només pots enviar missatges SMS des de la teva aplicació Missatges personal."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Utilitza el navegador personal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Utilitza el navegador de treball"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Truca"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Treball 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Prova"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Compartit"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de treball"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espai privat"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Comunitari"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"S\'ha amagat contingut sensible de les notificacions"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Contingut de l\'aplicació amagat de la compartició de pantalla per motius de seguretat"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"S\'ha connectat automàticament a un satèl·lit"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Torna a configurar Desbloqueig amb empremta digital"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> no funcionava correctament i s\'ha suprimit. Torna a configurar-la per desbloquejar el telèfon amb l\'empremta digital."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> no funcionaven correctament i s\'han suprimit. Torna a configurar-les per desbloquejar el telèfon amb l\'empremta digital."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Torna a configurar Desbloqueig facial"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"El teu model facial no funcionava correctament i s\'ha suprimit. Torna a configurar-lo per desbloquejar el telèfon amb la cara."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configura"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ara no"</string> </resources> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index d6565c1d78b5..ec7bb29a2ebe 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -155,31 +155,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepřesměrováno"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Zabezpečení mobilních sítí"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Šifrování, oznámení o nezašifrovaných sítích"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Přístup k ID zařízení"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"V <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> zaznamenala síť v okolí při použití SIM karty <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> jedinečný identifikátor vašeho zařízení (IMSI nebo IMEI)"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"V <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> zaznamenala síť v okolí při použití SIM karty <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> jedinečný identifikátor vašeho zařízení (IMSI nebo IMEI).\n\nTo znamená, že byla zaprotokolována vaše poloha, aktivita nebo identita. Jedná se o obvyklý postup, ale pro uživatele, kterým záleží na ochraně soukromí, to může být problém."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Připojení k zašifrované síti <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Připojení k SIM kartě <xliff:g id="NETWORK_NAME">%1$s</xliff:g> je teď bezpečnější"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Připojení k nezašifrované síti"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Hovory, zprávy a data jsou teď při používání SIM karty <xliff:g id="NETWORK_NAME">%1$s</xliff:g> zranitelnější"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Hovory, zprávy a data jsou teď při používání SIM karty <xliff:g id="NETWORK_NAME">%1$s</xliff:g> zranitelnější.\n\nJakmile bude připojení opět šifrováno, dostanete další oznámení."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Nastavení zabezpečení mobilních sítí"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Další informace"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Rozumím"</string> <string name="fcComplete" msgid="1080909484660507044">"Požadavek zadaný pomocí kódu funkce byl úspěšně dokončen."</string> <string name="fcError" msgid="5325116502080221346">"Problém s připojením nebo neplatný kód funkce."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -297,6 +285,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Hlas. asistence"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zamknout"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nové oznámení"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyzická klávesnice"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Zabezpečení"</string> @@ -2158,6 +2148,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Informační oznámení režimu sledu činností"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Zapnul se spořič baterie"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Je třeba snížit využití baterie za účelem prodloužení její výdrže"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Spořič baterie je zapnutý"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Spořič baterie je zapnutý a prodlužuje výdrž baterie"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Spořič baterie"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Spořič baterie je vypnutý"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon je dostatečně nabitý. Funkce už nejsou omezeny."</string> @@ -2232,10 +2224,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Přepnout na pracovní aplikaci?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Vaše organizace dovoluje volat jen z pracovních aplikací"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Vaše organizace dovoluje odesílat zprávy jen z pracovních aplikací"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Telefonovat můžete pouze z osobní aplikace Telefon. Takto uskutečněné hovory budou přidány do vaší osobní historie volání."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Zprávy SMS můžete odesílat jen z osobní aplikace Zprávy."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Použít osobní prohlížeč"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Použít pracovní prohlížeč"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Volat"</string> @@ -2415,8 +2405,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Práce 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Komunální"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Pracovní profil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Soukromý prostor"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Komunální"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Obsah citlivých oznámení je skrytý"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Obsah aplikace je z bezpečnostních důvodů při sdílení obrazovky skryt"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automaticky připojeno k satelitu"</string> @@ -2424,4 +2417,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Opětovné nastavení odemknutí otiskem prstu"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nefungoval správně a byl vymazán. Pokud chcete telefon odemykat otiskem prstu, nastavte jej znovu."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nefungovaly správně a byly vymazány. Pokud chcete telefon odemykat otiskem prstu, nastavte je znovu."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Nastavte odemknutí obličejem znovu"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Váš model obličeje nefungoval správně a byl vymazán. Pokud chcete telefon odemykat obličejem, nastavte jej znovu."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Nastavit"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Teď ne"</string> </resources> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 9fb4605683d7..ef4126283fa9 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderestillet"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobilnetværkssikkerhed"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Kryptering, notifikationer for ikke-krypterede netværk"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Enheds-id\'et blev tilgået"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Kl. <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerede et netværk i nærheden din enheds unikke id (IMSI eller IMEI), da du brugte dit <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Kl. <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> registrerede et netværk i nærheden din enheds unikke id (IMSI eller IMEI), da du brugte dit <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM.\n\nDet vil sige, at din lokation, din aktivitet eller din identitet er blevet logget. Dette er helt normalt, men kan være et problem for folk, der er bekymrede for deres privatliv."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Der er oprettet forbindelse til det krypterede netværk <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-forbindelsen er nu mere sikker"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Der er oprettet forbindelse til et ikke-krypteret netværk"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Opkald, beskeder og data er i øjeblikket mere sårbare, når du bruger dit <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Opkald, beskeder og data er i øjeblikket mere sårbare, når du bruger dit <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM.\n\nNår din forbindelse er krypteret igen, får du en ny notifikation."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Indstillinger for mobilnetværkssikkerhed"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Få flere oplysninger"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Funktionskoden er komplet."</string> <string name="fcError" msgid="5325116502080221346">"Forbindelsesproblemer eller ugyldig funktionskode."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Taleassistent"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Låsning"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Ny notifikation"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysisk tastatur"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sikkerhed"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Tag screenshot"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan tage et screenshot af skærmen."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Preview, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"luk"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"deaktivere eller redigere statuslinje"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Tillader, at appen kan deaktivere statusbjælken eller tilføje og fjerne systemikoner."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"vær statusbjælken"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notifikation med oplysninger om rutinetilstand"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterisparefunktion er aktiveret"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducerer batteriforbruget for at forlænge batteritiden"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterisparefunktion er aktiveret"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterisparefunktion er aktiveret for at forlænge batteritiden"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparefunktion"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterisparefunktion blev slået fra"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonen har tilstrækkeligt batteri. Funktionerne er ikke længere begrænsede."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Vil du skifte til en arbejdsapp?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Din organisation tillader kun, at du foretager opkald via arbejdsapps"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Din organisation tillader kun, at du sender beskeder via arbejdsapps"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Du kan kun foretage telefonopkald fra din personlige opkaldsapp. Opkald, der foretages med din personlige opkaldsapp, føjes til din personlige opkaldshistorik."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Du kan kun sende sms-beskeder fra din personlige beskedapp."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Brug personlig browser"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Brug arbejdsbrowser"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Ring op"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Arbejde 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Fælles"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Arbejdsprofil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privat område"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Fælles"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Følsomt indhold i notifikationen er skjult"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Af sikkerhedsmæssige årsager vises appindhold ikke ved skærmdeling"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Der blev automatisk oprettet forbindelse til satellit"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfigurer fingeroplåsning igen"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> virkede ikke optimalt og er derfor slettet. Konfigurer den igen for at bruge fingeroplåsning på din telefon."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> virkede ikke optimalt og er derfor slettet. Konfigurer dem igen for at bruge dit fingeraftryk til at låse din telefon op."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfigurer ansigtsoplåsning igen"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Din ansigtsmodel virkede ikke optimalt og er derfor slettet. Konfigurer den igen for at bruge ansigtsoplåsning på din telefon."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Konfigurer"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ikke nu"</string> </resources> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 7210fa7a9e2f..1df8954ef78d 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> nach <xliff:g id="TIME_DELAY">{2}</xliff:g> Sekunden."</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nicht weitergeleitet"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nicht weitergeleitet"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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 <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 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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Ok"</string> <string name="fcComplete" msgid="1080909484660507044">"Funktionscode abgeschlossen"</string> <string name="fcError" msgid="5325116502080221346">"Verbindungsproblem oder ungültiger Funktionscode"</string> <string name="httpErrorOk" msgid="6206751415788256357">"Ok"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Sprachassistent"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Sperren"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Neue Benachrichtigung"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physische Tastatur"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sicherheit"</string> @@ -1154,12 +1144,12 @@ <string name="year" msgid="5182610307741238982">"Jahr"</string> <string name="years" msgid="5797714729103773425">"Jahre"</string> <string name="now_string_shortest" msgid="3684914126941650330">"Jetzt"</string> - <string name="duration_minutes_shortest" msgid="5744379079540806690">"<xliff:g id="COUNT">%d</xliff:g> min"</string> - <string name="duration_hours_shortest" msgid="1477752094141971675">"<xliff:g id="COUNT">%d</xliff:g> h"</string> + <string name="duration_minutes_shortest" msgid="5744379079540806690">"<xliff:g id="COUNT">%d</xliff:g> Min."</string> + <string name="duration_hours_shortest" msgid="1477752094141971675">"<xliff:g id="COUNT">%d</xliff:g> Std."</string> <string name="duration_days_shortest" msgid="4083124701676227233">"<xliff:g id="COUNT">%d</xliff:g> T"</string> <string name="duration_years_shortest" msgid="483982719231145618">"<xliff:g id="COUNT">%d</xliff:g> J"</string> - <string name="duration_minutes_shortest_future" msgid="5260857299282734759">"in <xliff:g id="COUNT">%d</xliff:g> min"</string> - <string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g> h"</string> + <string name="duration_minutes_shortest_future" msgid="5260857299282734759">"in <xliff:g id="COUNT">%d</xliff:g> Min."</string> + <string name="duration_hours_shortest_future" msgid="2979276794547981674">"in <xliff:g id="COUNT">%d</xliff:g> Std."</string> <string name="duration_days_shortest_future" msgid="3392722163935571543">"in <xliff:g id="COUNT">%d</xliff:g> T"</string> <string name="duration_years_shortest_future" msgid="5537464088352970388">"in <xliff:g id="COUNT">%d</xliff:g> J"</string> <string name="duration_minutes_relative" msgid="8620337701051015593">"{count,plural, =1{Vor # Minute}other{Vor # Minuten}}"</string> @@ -2009,7 +1999,7 @@ <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Notruf"</string> <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Displaysperre einrichten"</string> <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Displaysperre einrichten"</string> - <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Richte zur Nutzung des privaten Bereichs auf dem Gerät die Displaysperre ein"</string> + <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Richte zur Nutzung des vertraulichen Profils auf dem Gerät die Displaysperre ein"</string> <string name="app_blocked_title" msgid="7353262160455028160">"App ist nicht verfügbar"</string> <string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> ist derzeit nicht verfügbar."</string> <string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> nicht verfügbar"</string> @@ -2156,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Infomitteilung zum Ablaufmodus"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Energiesparmodus aktiviert"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzierung der Akkunutzung, um die Akkulaufzeit zu verlängern"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Energiesparmodus ist aktiviert"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Der Energiesparmodus ist aktiviert, um die Akkulaufzeit zu verlängern"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Energiesparmodus"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Energiesparmodus deaktiviert"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Das Smartphone ist ausreichend geladen. Es sind keine Funktionen mehr beschränkt."</string> @@ -2230,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Zu geschäftlicher App wechseln?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Deine Organisation lässt das Telefonieren nur über geschäftliche Apps zu"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Deine Organisation lässt das Senden von Nachrichten nur über geschäftliche Apps zu"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Du kannst nur über deine persönliche Telefon App telefonieren. Über die persönliche Telefon App getätigte Anrufe werden deiner persönlichen Anrufliste hinzugefügt."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Du kannst nur über deine persönliche Messages App SMS senden."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Privaten Browser verwenden"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Arbeitsbrowser verwenden"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Anrufen"</string> @@ -2406,15 +2396,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Tastaturlayout festgelegt auf <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Zum Ändern tippen."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Physische Tastaturen konfiguriert"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Zum Ansehen der Tastaturen tippen"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Privat"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Vertraulich"</string> <string name="profile_label_clone" msgid="769106052210954285">"Klon"</string> <string name="profile_label_work" msgid="3495359133038584618">"Geschäftlich"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Geschäftlich 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Geschäftlich 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Gemeinsam genutzt"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Arbeitsprofil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Vertrauliches Profil"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Gemeinsam genutzt"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Vertrauliche Benachrichtigungsinhalte ausgeblendet"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App-Inhalte werden aus Sicherheitsgründen bei der Bildschirmfreigabe ausgeblendet"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatisch mit Satellit verbunden"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Entsperrung per Fingerabdruck neu einrichten"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> hat nicht einwandfrei funktioniert und wurde gelöscht. Richte ihn noch einmal ein, um dein Smartphone per Fingerabdruck zu entsperren."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> und <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> haben nicht einwandfrei funktioniert und wurden gelöscht. Richte sie noch einmal ein, um dein Smartphone per Fingerabdruck zu entsperren."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Entsperrung per Gesichtserkennung neu einrichten"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Dein Gesichtsmodell hat nicht einwandfrei funktioniert und wurde gelöscht. Richte es noch einmal ein, um dein Smartphone per Gesichtserkennung zu entsperren."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Einrichten"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Nicht jetzt"</string> </resources> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index c58537690d4a..3c7d16714849 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Ασφάλεια δικτύου κινητής τηλεφωνίας"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Κρυπτογράφηση, ειδοποιήσεις για μη κρυπτογραφημένα δίκτυα"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Πρόσβαση στο αναγνωριστικό συσκευής"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Στις <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, ένα κοντινό δίκτυο κατέγραψε το μοναδικό αναγνωριστικό της συσκευής σας (IMSI ή IMEI) κατά τη χρήση της SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Το κατάλαβα"</string> <string name="fcComplete" msgid="1080909484660507044">"Ο κωδικός λειτουργίας ολοκληρώθηκε."</string> <string name="fcError" msgid="5325116502080221346">"Πρόβλημα σύνδεσης ή μη έγκυρος κώδικας δυνατότητας."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Φων.υποβοηθ."</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Κλείδωμα"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Νέα ειδοποίηση"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Κανονικό πληκτρολόγιο"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Ασφάλεια"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Μπορείτε να κάνετε τηλεφωνικές κλήσεις μόνο από την προσωπική εφαρμογή Τηλέφωνο. Οι κλήσεις που γίνονται με την προσωπική εφαρμογή Τηλέφωνο θα προστίθενται στο προσωπικό ιστορικό κλήσεων."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Μπορείτε να στέλνετε μηνύματα SMS μόνο από την προσωπική εφαρμογή Messages."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Χρήση προσωπικού προγράμματος περιήγησης"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Χρήση προγράμματος περιήγησης εργασίας"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Κλήση"</string> @@ -2413,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Εργασία 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Δοκιμή"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Κοινόχρηστο"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Προφίλ εργασίας"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ιδιωτικός χώρος"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Κλώνος"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Κοινόχρηστο"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Έγινε απόκρυψη της ειδοποίησης ευαίσθητου περιεχομένου"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Για λόγους ασφάλειας, έγινε απόκρυψη του περιεχομένου της εφαρμογής από την κοινή χρήση οθόνης"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Συνδέθηκε αυτόματα με δορυφόρο"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Επαναρρύθμιση λειτουργίας Ξεκλείδωμα με δακτυλικό αποτύπωμα"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Το δακτυλικό αποτύπωμα <xliff:g id="FINGERPRINT">%s</xliff:g> δεν λειτουργούσε καλά και διαγράφηκε. Ρυθμίστε το ξανά για να ξεκλειδώνετε το τηλέφωνο με το δακτυλικό αποτύπωμά σας."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Τα δακτυλικά αποτυπώματα <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> και <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> δεν λειτουργούσαν καλά και διαγράφηκαν. Ρυθμίστε τα ξανά για να ξεκλειδώνετε το τηλέφωνο με το δακτυλικό αποτύπωμά σας."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Επαναρρύθμιση λειτουργίας Ξεκλείδωμα με το πρόσωπο"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Το μοντέλο προσώπου δεν λειτουργούσε καλά και διαγράφηκε. Ρυθμίστε το ξανά για να ξεκλειδώνετε το τηλέφωνο με το πρόσωπό σας."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Ρύθμιση"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Όχι τώρα"</string> </resources> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index aa22491fb7d4..5f7dd656d0ca 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobile network security"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encryption, notifications for unencrypted networks"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Device ID accessed"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"At <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, a nearby network recorded your device\'s unique ID (IMSI or IMEI) while using your <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"At <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, a nearby network recorded your device\'s unique ID (IMSI or IMEI) while using your <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nThis means that your location, activity or identity have been logged. This is common practice but may be an issue for people concerned about privacy."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connected to encrypted network <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 connection is more secure now"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connected to unencrypted network"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Calls, messages and data are currently more vulnerable while using your <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Calls, messages and data are currently more vulnerable while using your <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nWhen your connection is encrypted again, you\'ll get another notification."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobile network security settings"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Learn more"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Got it"</string> <string name="fcComplete" msgid="1080909484660507044">"Feature code complete."</string> <string name="fcError" msgid="5325116502080221346">"Connection problem or invalid feature code."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Security"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Can take a screenshot of the display."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Preview, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"dismiss"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"disable or modify status bar"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Allows the app to disable the status bar or add and remove system icons."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"be the status bar"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Switch to work app?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Your organisation only allows you to make calls from work apps"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Your organisation only allows you to send messages from work apps"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"You can only make phone calls from your personal Phone app. Calls made with your personal Phone app will be added to your personal call history."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"You can only send SMS messages from your personal Messages app."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Use personal browser"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Use work browser"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Call"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Communal"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitive notification content hidden"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App content hidden from screen share for security"</string> @@ -2422,4 +2415,15 @@ <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> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <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 your 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 your 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-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml index ad376b8def27..d1894b5e7106 100644 --- a/core/res/res/values-en-rCA/strings.xml +++ b/core/res/res/values-en-rCA/strings.xml @@ -283,6 +283,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Reply"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Security"</string> @@ -2144,6 +2145,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string> @@ -2399,6 +2402,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Communal"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitive notification content hidden"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App content hidden from screen share for security"</string> @@ -2407,4 +2414,15 @@ <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> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <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 e9bd5b3a0056..12fd027cdf2d 100644 --- a/core/res/res/values-en-rGB/strings.xml +++ b/core/res/res/values-en-rGB/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobile network security"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encryption, notifications for unencrypted networks"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Device ID accessed"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"At <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, a nearby network recorded your device\'s unique ID (IMSI or IMEI) while using your <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"At <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, a nearby network recorded your device\'s unique ID (IMSI or IMEI) while using your <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nThis means that your location, activity or identity have been logged. This is common practice but may be an issue for people concerned about privacy."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connected to encrypted network <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 connection is more secure now"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connected to unencrypted network"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Calls, messages and data are currently more vulnerable while using your <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Calls, messages and data are currently more vulnerable while using your <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nWhen your connection is encrypted again, you\'ll get another notification."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobile network security settings"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Learn more"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Got it"</string> <string name="fcComplete" msgid="1080909484660507044">"Feature code complete."</string> <string name="fcError" msgid="5325116502080221346">"Connection problem or invalid feature code."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Security"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Can take a screenshot of the display."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Preview, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"dismiss"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"disable or modify status bar"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Allows the app to disable the status bar or add and remove system icons."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"be the status bar"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Switch to work app?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Your organisation only allows you to make calls from work apps"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Your organisation only allows you to send messages from work apps"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"You can only make phone calls from your personal Phone app. Calls made with your personal Phone app will be added to your personal call history."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"You can only send SMS messages from your personal Messages app."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Use personal browser"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Use work browser"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Call"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Communal"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitive notification content hidden"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App content hidden from screen share for security"</string> @@ -2422,4 +2415,15 @@ <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> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <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 your 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 your 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-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml index ec45a4ccb733..129310eec160 100644 --- a/core/res/res/values-en-rIN/strings.xml +++ b/core/res/res/values-en-rIN/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> after <xliff:g id="TIME_DELAY">{2}</xliff:g> seconds"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Not forwarded"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobile network security"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encryption, notifications for unencrypted networks"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Device ID accessed"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"At <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, a nearby network recorded your device\'s unique ID (IMSI or IMEI) while using your <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"At <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, a nearby network recorded your device\'s unique ID (IMSI or IMEI) while using your <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nThis means that your location, activity or identity have been logged. This is common practice but may be an issue for people concerned about privacy."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connected to encrypted network <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 connection is more secure now"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connected to unencrypted network"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Calls, messages and data are currently more vulnerable while using your <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Calls, messages and data are currently more vulnerable while using your <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM.\n\nWhen your connection is encrypted again, you\'ll get another notification."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobile network security settings"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Learn more"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Got it"</string> <string name="fcComplete" msgid="1080909484660507044">"Feature code complete."</string> <string name="fcError" msgid="5325116502080221346">"Connection problem or invalid feature code."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Security"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Take screenshot"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Can take a screenshot of the display."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Preview, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"dismiss"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"disable or modify status bar"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Allows the app to disable the status bar or add and remove system icons."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"be the status bar"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Switch to work app?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Your organisation only allows you to make calls from work apps"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Your organisation only allows you to send messages from work apps"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"You can only make phone calls from your personal Phone app. Calls made with your personal Phone app will be added to your personal call history."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"You can only send SMS messages from your personal Messages app."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Use personal browser"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Use work browser"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Call"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Communal"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitive notification content hidden"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App content hidden from screen share for security"</string> @@ -2422,4 +2415,15 @@ <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> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <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 your 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 your 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-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml index 4c72d6a02d9c..e08f93466a0e 100644 --- a/core/res/res/values-en-rXC/strings.xml +++ b/core/res/res/values-en-rXC/strings.xml @@ -283,6 +283,7 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <string name="notification_compact_heads_up_reply" msgid="2425293958371284340">"Reply"</string> <string name="notification_hidden_text" msgid="2835519769868187223">"New notification"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Physical keyboard"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Security"</string> @@ -2144,6 +2145,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Routine Mode info notification"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Battery Saver turned on"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reducing battery usage to extend battery life"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Battery Saver is on"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Battery Saver is turned on to extend battery life"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Battery Saver"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Battery Saver turned off"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Phone has enough charge. Features no longer restricted."</string> @@ -2399,6 +2402,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Work 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Work profile"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Private space"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Communal"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitive notification content hidden"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App content hidden from screen share for security"</string> @@ -2407,4 +2414,15 @@ <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> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <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 387381f99ff5..30a0457201b4 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> después de <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: no se ha remitido"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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 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> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La conexión de la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ahora es más segura"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conexión con una red no encriptada"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Cuando usas la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, las llamadas, los mensajes y los datos son más vulnerables"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Cuando usas la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>, las llamadas, los mensajes y los datos son más vulnerables.\n\nRecibirás otra notificación cuando se vuelva a encriptar tu conexión."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Configuración de la seguridad de redes móviles"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Más información"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendido"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de función completo."</string> <string name="fcError" msgid="5325116502080221346">"Problema de conexión o código de función no válido."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Aceptar"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Asistente voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueo"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nueva"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Tomar captura de pantalla"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Puede tomar capturas de pantalla."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Vista previa, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"descartar"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar o modificar la barra de estado"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite que la aplicación inhabilite la barra de estado o que agregue y elimine íconos del sistema."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"aparecer en la barra de estado"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificación de información del modo de Rutinas"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ahorro de batería activado"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduciendo el uso de la batería para extender su duración"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"El Ahorro de batería está activado"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Se activó el Ahorro de batería para extender la duración de batería"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ahorro de batería"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Se desactivó el Ahorro de batería"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El teléfono tiene suficiente carga. Ya no se restringen las funciones."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"¿Quieres cambiar a una app de trabajo?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Tu organización solo te permite realizar llamadas desde apps de trabajo"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Tu organización solo te permite enviar mensajes desde apps de trabajo"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Solo puedes realizar llamadas desde tu app de Teléfono personal. Las llamadas que hagas con el Teléfono personal se agregarán a tu historial de llamadas personal."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Solo puedes enviar mensajes SMS desde tu app de Mensajes personal."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Usar un navegador personal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Usar un navegador de trabajo"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Llamar"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Trabajo 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Probar"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Compartido"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabajo"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espacio privado"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Compartido"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Se ocultó contenido sensible de la notificación"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Se ocultó el contenido de la app durante el uso compartido de la pantalla por motivos de seguridad"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conexión automática a satélite"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Vuelve a configurar el Desbloqueo con huellas dactilares"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Se borró <xliff:g id="FINGERPRINT">%s</xliff:g> porque no funcionaba correctamente. Vuelve a configurarla para desbloquear el teléfono con la huella dactilar."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Se borraron <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> y <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> porque no funcionaban correctamente. Vuelve a configurarlas para desbloquear el teléfono con la huella dactilar."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Vuelve a configurar el Desbloqueo facial"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Se borró tu modelo de rostro porque no funcionaba correctamente. Vuelve a configurarlo para desbloquear el teléfono con el rostro."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configurar"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ahora no"</string> </resources> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index bad3cdd90c97..5079fdf415f9 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> transcurridos <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: No desviada"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Seguridad de redes móviles"</string> + <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 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> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Tus llamadas, mensajes y datos son más vulnerables mientras uses la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Tus llamadas, mensajes y datos son más vulnerables mientras uses la SIM de <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nCuando tu conexión vuelva a cifrarse, recibirás una notificación."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Ajustes de seguridad de redes móviles"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Más información"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendido"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de función completo"</string> <string name="fcError" msgid="5325116502080221346">"Se ha producido un problema de conexión o el código de la función no es válido."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Aceptar"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Asistente voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueo de seguridad"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"> 999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nueva"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Hacer captura"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Puede hacer capturas de la pantalla."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Vista previa, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"cerrar"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"inhabilitar o modificar la barra de estado"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite que la aplicación inhabilite la barra de estado o añada y elimine iconos del sistema."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"aparecer en la barra de estado"</string> @@ -1415,7 +1404,7 @@ <string name="usb_unsupported_audio_accessory_title" msgid="2335775548086533065">"Se ha detectado un accesorio de audio analógico"</string> <string name="usb_unsupported_audio_accessory_message" msgid="1300168007129796621">"El dispositivo adjunto no es compatible con este teléfono. Toca para obtener más información."</string> <string name="adb_active_notification_title" msgid="408390247354560331">"Depuración por USB activa"</string> - <string name="adb_active_notification_message" msgid="5617264033476778211">"Toca para desactivar la depuración USB"</string> + <string name="adb_active_notification_message" msgid="5617264033476778211">"Toca para desactivar la depuración por USB"</string> <string name="adb_active_notification_message" product="tv" msgid="6624498401272780855">"Seleccionar para inhabilitar la depuración por USB"</string> <string name="adbwifi_active_notification_title" msgid="6147343659168302473">"Depuración inalámbrica conectada"</string> <string name="adbwifi_active_notification_message" msgid="930987922852867972">"Toca para desactivar la depuración inalámbrica"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificación sobre el modo rutina"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ahorro de batería activado"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduciendo el uso de batería para prolongar su duración"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Ahorro de batería activado"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Se ha activado Ahorro de batería para prolongar la duración de la batería"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Ahorro de batería"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Ahorro de batería desactivado"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"El teléfono tiene suficiente batería. Las funciones ya no están restringidas."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"¿Cambiar a la aplicación de trabajo?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Tu organización solo te permite hacer llamadas desde aplicaciones de trabajo"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Tu organización solo te permite enviar mensajes desde aplicaciones de trabajo"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Solo puedes hacer llamadas desde tu aplicación personal Teléfono. Las llamadas que hagas desde Teléfono se añadirán a tu historial de llamadas."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Solo puedes enviar mensajes SMS desde tu aplicación personal Mensajes."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Usar navegador personal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Usar navegador de trabajo"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Llamar"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Trabajo 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Prueba"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Común"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabajo"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espacio privado"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Común"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Contenido sensible de la notificación oculto"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Contenido de la aplicación oculto en pantalla compartida por motivos de seguridad"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conectado automáticamente al satélite"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configura Desbloqueo con huella digital de nuevo"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> no funcionaba correctamente y se ha eliminado. Configúrala de nuevo para desbloquear el teléfono con la huella digital."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> y <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> no funcionaban correctamente y se han eliminado. Configúralas de nuevo para desbloquear el teléfono con la huella digital."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Configura Desbloqueo facial de nuevo"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Tu modelo facial no funcionaba correctamente y se ha eliminado. Configúralo de nuevo para desbloquear el teléfono con la cara."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configurar"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ahora no"</string> </resources> diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml index 7364183b901c..16b0ea25b158 100644 --- a/core/res/res/values-et/strings.xml +++ b/core/res/res/values-et/strings.xml @@ -153,31 +153,19 @@ <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> sekundi pärast"</string> <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobiilsidevõrgu turve"</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> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Ühendatud krüpteeritud võrguga <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Võrgu <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-i ühendus on nüüd turvalisem"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Ühendatud krüpteerimata võrguga"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Võrgu <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-i kasutamisel on teie kõned, sõnumid ja andmed praegu haavatavamad"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Võrgu <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-i kasutamisel on teie kõned, sõnumid ja andmed praegu haavatavamad.\n\nKui teie ühendus on taas krüpteeritud, saate veel ühe märguande."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobiilsidevõrgu turvaseaded"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Lisateave"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Selge"</string> <string name="fcComplete" msgid="1080909484660507044">"Funktsioonikood valmis."</string> <string name="fcError" msgid="5325116502080221346">"Ühendusprobleem või kehtetu funktsioonikood."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Häälabi"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lukusta"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Uus märguanne"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Füüsiline klaviatuur"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Turvalisus"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Jäädvusta ekraanipilt"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Saab jäädvustada ekraanipildi."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Eelvaade, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"loobu"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"keela või muuda olekuriba"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Võimaldab rakendusel keelata olekuriba või lisada ja eemaldada süsteemiikoone."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"olekuribana kuvamine"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rutiinirežiimi teabe märguanne"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Akusäästja lülitati sisse"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Aku tööea pikendamiseks vähendatakse akukasutust"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Akusäästja on sisse lülitatud"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Akusäästja on sisse lülitatud, et pikendada aku tööiga"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akusäästja"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akusäästja on välja lülitatud"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon on piisavalt laetud. Funktsioonid ei ole enam piiratud."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Kas lülituda töörakendusele?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Teie organisatsioon lubab helistada ainult töörakendustest."</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Teie organisatsioon lubab sõnumeid saata ainult töörakendustest."</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Saate helistada ainult oma isiklikust rakendusest Telefon. Isikliku rakendusesega Telefon tehtud kõned lisatakse teie isiklikku kõneajalukku."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Saate saata SMS-sõnumeid ainult oma isiklikust rakendusest Messages."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Kasuta isiklikku brauserit"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Kasuta tööbrauserit"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Helista"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Töö 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Jagatud"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Tööprofiil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privaatne ruum"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Kloon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Ühine"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Märguande delikaatne sisu peideti"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Rakenduse sisu on ekraani jagamises turvalisuse huvides peidetud"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Seadistage sõrmejäljega avamine uuesti"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ei töötanud hästi ja kustutati. Telefoni sõrmejäljega avamiseks seadistage see uuesti."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ja <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ei töötanud hästi ning kustutati. Telefoni sõrmejäljega avamiseks seadistage need uuesti."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Seadistage näoga avamine uuesti"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Teie näomudel ei töötanud hästi ja kustutati. Telefoni näoga avamiseks seadistage see uuesti."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Seadista"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Mitte praegu"</string> </resources> diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml index b4c054ba7bca..4fb4726dce87 100644 --- a/core/res/res/values-eu/strings.xml +++ b/core/res/res/values-eu/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> zenbakira <xliff:g id="TIME_DELAY">{2}</xliff:g> segundotan"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ez da desbideratu"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ez da desbideratu"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sare mugikorraren segurtasuna"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Enkriptatzea, enkriptatu gabeko sareen jakinarazpenak"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Gailu-identifikatzailea atzitu da"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> aldera, inguruko sare batek zure gailuaren identifikatzaile esklusiboa (IMSI edo IMEI) erregistratu du, <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> operadorearen SIMa erabiltzen ari zinenean"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> aldera, inguruko sare batek zure gailuaren identifikatzaile esklusiboa (IMSI edo IMEI) erregistratu du, <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> operadorearen SIMa erabiltzen ari zinenean.\n\nHorrek esan nahi du zure kokapena, jarduerak edo identitatea erregistratuta geratu direla. Ohikoa da hori, baina baliteke arazo bat izatea pribatutasunari garrantzia ematen dioten pertsonentzat."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> sare enkriptatura konektatuta"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> operadorearen SIMaren konexioa seguruagoa da orain"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Enkriptatu gabeko sare batera konektatuta"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Deiek, mezuek eta datuek arrisku handiagoa dute orain, <xliff:g id="NETWORK_NAME">%1$s</xliff:g> operadorearen SIMa erabiltzen ari zarelako"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Deiek, mezuek eta datuek arrisku handiagoa dute orain, <xliff:g id="NETWORK_NAME">%1$s</xliff:g> operadorearen SIMa erabiltzen ari zarelako.\n\nBeste jakinarazpen bat jasoko duzu konexioa berriro ere enkriptatuta dagoenean."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Sare mugikorraren segurtasun-ezarpenak"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Lortu informazio gehiago"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Ados"</string> <string name="fcComplete" msgid="1080909484660507044">"Eginbide-kodea osatu da."</string> <string name="fcError" msgid="5325116502080221346">"Konexio-arazo bat gertatu da edo eginbide-kodea baliogabea da."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Ados"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ahots-laguntza"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blokeatu"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Jakinarazpen berria"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teklatu fisikoa"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Segurtasuna"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Pantaila-argazkiak atera."</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pantaila-argazkiak atera ditzake."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Aurrebista, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"baztertu"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"desgaitu edo aldatu egoera-barra"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Egoera-barra desgaitzeko edo sistema-ikonoak gehitzeko edo kentzeko baimena ematen dio aplikazioari."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"bihurtu egoera-barra"</string> @@ -480,9 +469,9 @@ <string name="permdesc_broadcastSticky" product="tv" msgid="2338185920171000650">"Igorpen iraunkorrak egiteko baimena ematen dio aplikazioari. Igorpena amaitu ondoren ere igortzen jarraitzen dute igorpen iraunkorrek. Gehiegi erabiliz gero, Android TV gailua motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string> <string name="permdesc_broadcastSticky" product="default" msgid="134529339678913453">"Igorpen iraunkorrak emateko baimena ematen dio aplikazioari; horiek igorpena amaitu ondoren mantentzen dira. Gehiegi erabiliz gero, telefonoa motel edo ezegonkor ibiliko da, memoria gehiago erabiliko delako."</string> <string name="permlab_readContacts" msgid="8776395111787429099">"irakurri kontaktuak"</string> - <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> - <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> - <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan gordetako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> + <string name="permdesc_readContacts" product="tablet" msgid="6430093481659992692">"Tabletan biltegiratutako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten tabletako kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> + <string name="permdesc_readContacts" product="tv" msgid="8400138591135554789">"Android TV gailuan biltegiratutako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten Android TV gailuko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> + <string name="permdesc_readContacts" product="default" msgid="4911989776203207644">"Telefonoan biltegiratutako kontaktuei buruzko datuak irakurtzeko baimena ematen dio aplikazioari. Kontaktuak sortu dituzten telefonoko kontuak ere atzitu ahalko ditu aplikazioak. Horrek barnean hartuko ditu instalatutako aplikazioak sortutako kontuak, agian. Baimen horrekin, kontaktuen datuak gorde ditzake aplikazioak, eta baliteke asmo txarreko aplikazioek zuk jakin gabe partekatzea datu horiek."</string> <string name="permlab_writeContacts" msgid="8919430536404830430">"aldatu kontaktuak"</string> <string name="permdesc_writeContacts" product="tablet" msgid="6422419281427826181">"Tabletan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioak kontaktuen datuak ezaba ditzake."</string> <string name="permdesc_writeContacts" product="tv" msgid="6488872735379978935">"Android TV gailuan gordetako kontaktuei buruzko datuak aldatzeko baimena ematen dio aplikazioari. Baimen horrekin, aplikazioak kontaktuen datuak ezaba ditzake."</string> @@ -498,9 +487,9 @@ <string name="permlab_bodySensors_background" msgid="4912560779957760446">"Atzitu gorputz-sentsoreen datuak (adib., bihotz-maiztasunarenak) atzeko planoan"</string> <string name="permdesc_bodySensors_background" product="default" msgid="8870726027557749417">"Aplikazioa atzeko planoan egon bitartean, gorputz-sentsoreen datuak (besteak beste, bihotz-maiztasuna, tenperatura eta odolean dagoen oxigenoaren ehunekoa) erabiltzeko baimena ematen dio aplikazioari."</string> <string name="permlab_readCalendar" msgid="6408654259475396200">"irakurri egutegiko gertaerak eta xehetasunak"</string> - <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Aplikazioak tabletan gordetako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string> - <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Aplikazioak Android TV gailuan gordeta dituzun egutegiko gertaerak irakur ditzake, baita egutegiko datuak partekatu eta gorde ere."</string> - <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"Aplikazioak telefonoan gordetako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string> + <string name="permdesc_readCalendar" product="tablet" msgid="515452384059803326">"Aplikazioak tabletan biltegiratutako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string> + <string name="permdesc_readCalendar" product="tv" msgid="5811726712981647628">"Aplikazioak Android TV gailuan biltegiratuta dituzun egutegiko gertaerak irakur ditzake, baita egutegiko datuak partekatu eta gorde ere."</string> + <string name="permdesc_readCalendar" product="default" msgid="9118823807655829957">"Aplikazioak telefonoan biltegiratutako egutegiko gertaerak irakur ditzake eta egutegiko datuak parteka eta gorde ditzake."</string> <string name="permlab_writeCalendar" msgid="6422137308329578076">"gehitu edo aldatu egutegiko gertaerak eta bidali mezu elektronikoak gonbidatuei jabeek jakin gabe"</string> <string name="permdesc_writeCalendar" product="tablet" msgid="8722230940717092850">"Tabletako gertaerak gehitzeko, kentzeko edo aldatzeko aukera du aplikazioak. Gainera, egutegien jabeenak diruditen mezuak bidal ditzake, eta gertaerak alda ditzake jabeei beraiei jakinarazi gabe."</string> <string name="permdesc_writeCalendar" product="tv" msgid="951246749004952706">"Android TV gailuan egutegiko gertaerak gehitzeko eta gehitutakoak kentzeko edo aldatzeko aukera dute aplikazioek. Gainera, egutegien jabeenak diruditen mezuak bidal ditzakete, edo gertaerak aldatu jabeei ezer esan gabe."</string> @@ -2157,6 +2146,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> + <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> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Laneko aplikaziora aldatu nahi duzu?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Laneko aplikazioetatik soilik deitzeko baimena ematen du zure erakundeak"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Mezuak laneko aplikazioetatik soilik bidaltzeko baimena ematen du zure erakundeak"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Telefono-deiak egiteko, Telefonoa aplikazio pertsonala soilik erabil dezakezu. Deien historia pertsonalean gehituko dira Telefonoa aplikazio pertsonalarekin egindako deiak."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS mezuak bidaltzeko, Mezuak aplikazio pertsonala soilik erabil dezakezu."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Erabili arakatzaile pertsonala"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Erabili laneko arakatzailea"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Deitu"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Lanekoa 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Probakoa"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Partekatua"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Laneko profila"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Eremu pribatua"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klona"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Partekatua"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Jakinarazpenaren kontuzko edukia ezkutatu da"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Aplikazioko edukia ezkutatu egin da pantaila partekatzeko eginbidetik, segurtasuna bermatzeko"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatikoki konektatu da satelitera"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfiguratu berriro hatz-marka bidez desblokeatzeko eginbidea"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ezabatu egin da, ez zuelako ondo funtzionatzen. Telefonoa hatz-markarekin desblokeatzeko, konfigura ezazu berriro."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> eta <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ezabatu egin dira, ez zutelako ondo funtzionatzen. Telefonoa hatz-markarekin desblokeatzeko, konfigura itzazu berriro."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfiguratu berriro aurpegi bidez desblokeatzeko eginbidea"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Aurpegi-eredua ezabatu egin da, ez zuelako ondo funtzionatzen. Telefonoa aurpegiarekin desblokeatzeko, konfigura ezazu berriro."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Konfiguratu"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Orain ez"</string> </resources> diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml index db0630915538..7272bcfcf8f3 100644 --- a/core/res/res/values-fa/strings.xml +++ b/core/res/res/values-fa/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"تنظیمات امنیت شبکه تلفن همراه"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"بیشتر بدانید"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"متوجهام"</string> <string name="fcComplete" msgid="1080909484660507044">"کد ویژگی کامل شد."</string> <string name="fcError" msgid="5325116502080221346">"مشکل در اتصال یا کد ویژگی نامعتبر."</string> <string name="httpErrorOk" msgid="6206751415788256357">"تأیید"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"دستیار صوتی"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"قفل همه"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"۹۹۹+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"اعلان جدید"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"صفحهکلید فیزیکی"</string> <string name="notification_channel_security" msgid="8516754650348238057">"امنیت"</string> @@ -678,7 +668,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> @@ -1916,8 +1906,8 @@ <string name="package_updated_device_owner" msgid="7560272363805506941">"توسط سرپرست سیستم بهروزرسانی شد"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"توسط سرپرست سیستم حذف شد"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"تأیید"</string> - <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"«بهینهسازی باتری» «زمینه تیره» را روشن میکند و فعالیت پسزمینه، برخی از جلوههای بصری، ویژگیهایی خاص، و برخی از اتصالهای شبکه را محدود یا خاموش میکند."</string> - <string name="battery_saver_description" msgid="8518809702138617167">"«بهینهسازی باتری» «زمینه تیره» را روشن میکند و فعالیت پسزمینه، برخی از جلوههای بصری، ویژگیهایی خاص، و برخی از اتصالهای شبکه را محدود یا خاموش میکند."</string> + <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"«بهینهسازی باتری» «زمینه تاریک» را روشن میکند و فعالیت پسزمینه، برخی از جلوههای بصری، ویژگیهایی خاص، و برخی از اتصالهای شبکه را محدود یا خاموش میکند."</string> + <string name="battery_saver_description" msgid="8518809702138617167">"«بهینهسازی باتری» «زمینه تاریک» را روشن میکند و فعالیت پسزمینه، برخی از جلوههای بصری، ویژگیهایی خاص، و برخی از اتصالهای شبکه را محدود یا خاموش میکند."</string> <string name="data_saver_description" msgid="4995164271550590517">"برای کمک به کاهش مصرف داده، «صرفهجویی داده» از ارسال و دریافت داده در پسزمینه در بعضی برنامهها جلوگیری میکند. برنامهای که درحالحاضر استفاده میکنید میتواند به دادهها دسترسی داشته باشد اما دفعات دسترسی آن محدود است. این میتواند به این معنی باشد که، برای مثال، تصاویر تازمانیکه روی آنها ضربه نزنید نشان داده نمیشوند."</string> <string name="data_saver_enable_title" msgid="7080620065745260137">"«صرفهجویی داده» روشن شود؟"</string> <string name="data_saver_enable_button" msgid="4399405762586419726">"روشن کردن"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"فقط از برنامه «تلفن» شخصیتان میتوانید تماس تلفنی برقرار کنید. تماسهای برقرارشده با برنامه «تلفن» شخصی به سابقه تماس شخصیتان اضافه خواهد شد."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"فقط از برنامه «پیامنگار» شخصیتان میتوانید پیامک ارسال کنید."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"استفاده از مرورگر شخصی"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"استفاده از مرورگر کاری"</string> <string name="miniresolver_call" msgid="6386870060423480765">"تماس گرفتن"</string> @@ -2407,14 +2397,17 @@ <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"صفحهکلیدهای فیزیکی پیکربندی شدند"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"برای مشاهده صفحهکلیدها ضربه بزنید"</string> <string name="profile_label_private" msgid="6463418670715290696">"خصوصی"</string> - <string name="profile_label_clone" msgid="769106052210954285">"مشابهسازی"</string> + <string name="profile_label_clone" msgid="769106052210954285">"همسانهسازی"</string> <string name="profile_label_work" msgid="3495359133038584618">"کار"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"کار ۲"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"کار ۳"</string> <string name="profile_label_test" msgid="9168641926186071947">"آزمایش"</string> <string name="profile_label_communal" msgid="8743921499944800427">"عمومی"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"نمایه کاری"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"فضای خصوصی"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"همسانهسازی"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"همگانی"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"محتوای اعلان حساس پنهان شده است"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"بهدلایل امنیتی، محتوای برنامه از دید همرسانی صفحهنمایش پنهان شد"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"بهطور خودکار به ماهواره متصل شد"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"راهاندازی مجدد «قفلگشایی با اثر انگشت»"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> خوب کار نمیکرد و حذف شد. برای باز کردن قفل تلفن با اثر انگشت، آن را دوباره راهاندازی کنید."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> و <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> خوب کار نمیکرد و حذف شد. برای باز کردن قفل تلفن با اثر انگشت، آنها را دوباره راهاندازی کنید."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"راهاندازی مجدد «قفلگشایی با چهره»"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"مدل چهره شما خوب کار نمیکرد و حذف شد. برای باز کردن قفل تلفن با چهره، دوباره آن را راهاندازی کنید."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"راهاندازی"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"حالا نه"</string> </resources> diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml index 1a6e7c427b57..7062a8955acb 100644 --- a/core/res/res/values-fi/strings.xml +++ b/core/res/res/values-fi/strings.xml @@ -153,31 +153,19 @@ <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> sekunnin päästä"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ei siirretty"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ei siirretty"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobiiliverkkoa koskeva turvallisuus"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Salaus, ilmoitukset salaamattomista verkoista"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Laitteen tunnus jaettu"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"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"</string> + <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 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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Lue lisää"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Ominaisuuskoodi valmis."</string> <string name="fcError" msgid="5325116502080221346">"Yhteysongelma tai virheellinen ominaisuuskoodi."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ääniapuri"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lukitse"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Uusi ilmoitus"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyysinen näppäimistö"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Turvallisuus"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ota kuvakaappaus"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Voi ottaa kuvakaappauksen näytöstä."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Esikatselu, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"ohita"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"poista tilapalkki käytöstä tai muokkaa tilapalkkia"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Antaa sovelluksen poistaa tilapalkin käytöstä ja lisätä tai poistaa järjestelmäkuvakkeita."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"sijaita tilapalkissa"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Ohjelmatilan tietoilmoitus"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Virransäästö päällä"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Akun käyttöä rajoitetaan akunkeston pidentämiseksi"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Virransäästö on päällä"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Virransäästö on laitettu päälle akunkeston pidentämiseksi"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Virransäästö"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Virransäästö laitettiin pois päältä"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Puhelimessa on tarpeeksi virtaa. Ominaisuuksia ei enää rajoiteta."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Vaihdetaanko työsovellukseen?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Organisaatio sallii soittamisen vain työsovelluksilla"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organisaatio sallii viestien lähettämisen vain työsovelluksilla"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Voit soittaa puheluita vain henkilökohtaisesta Puhelin-sovelluksesta. Sillä soitetut puhelut lisätään henkilökohtaiseen soittohistoriaasi."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Voit lähettää tekstiviestejä vain henkilökohtaisesta Messages-sovelluksesta."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Käytä henkilökohtaista selainta"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Käytä työselainta"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Soita"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Työ 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Testi"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Jaettu"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Työprofiili"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Yksityinen tila"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klooni"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Yhteinen"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Arkaluontoisen ilmoituksen sisältö piilotettu"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sovelluksen sisältö piilotettu näytön jakamiselta turvallisuussyistä"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Yhdistetty automaattisesti satelliittiin"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ota sormenjälkiavaus uudelleen käyttöön"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ei toiminut kunnolla, ja se poistettiin. Ota se uudelleen käyttöön, jotta voit avata puhelimen lukituksen sormenjäljellä."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ja <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> eivät toimineet kunnolla, ja ne poistettiin. Ota ne uudelleen käyttöön, jotta voit avata puhelimen lukituksen sormenjäljellä."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Ota kasvojentunnistusavaus uudelleen käyttöön"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Kasvomallisi ei toiminut kunnolla, ja se poistettiin. Ota se uudelleen käyttöön, jotta voit avata puhelimen lukituksen kasvoilla."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Ota käyttöön"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ei nyt"</string> </resources> diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml index 749fcf640c1d..bc61dead4cdb 100644 --- a/core/res/res/values-fr-rCA/strings.xml +++ b/core/res/res/values-fr-rCA/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : <xliff:g id="DIALING_NUMBER">{1}</xliff:g> au bout de <xliff:g id="TIME_DELAY">{2}</xliff:g> secondes"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sécurité des réseaux mobiles"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Chiffrement, notifications pour les réseaux non chiffrés"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Identifiant d\'appareil consulté"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"À <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, un réseau à proximité a enregistré l\'identifiant unique de votre appareil (IMSI ou IIEM) alors que vous utilisiez votre carte SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"À <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, un réseau à proximité a enregistré l\'identifiant unique de votre appareil (IMSI ou IIEM) alors que vous utilisiez votre carte SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nCela signifie que votre position, votre activité ou votre identité ont été journalisées. Il s\'agit d\'une pratique courante, mais qui peut poser problème aux personnes soucieuses de leur confidentialité."</string> + <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, 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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Code de service terminé"</string> <string name="fcError" msgid="5325116502080221346">"Problème de connexion ou code de service non valide"</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -264,7 +252,7 @@ <string name="reboot_safemode_title" msgid="5853949122655346734">"Redémarrer en mode sans échec"</string> <string name="reboot_safemode_confirm" msgid="1658357874737219624">"Voulez-vous redémarrer en mode sans échec? Cette opération aura pour effet de désactiver toutes les applications tierces que vous avez installées. Elles seront réactivées au prochain redémarrage."</string> <string name="recent_tasks_title" msgid="8183172372995396653">"Récents"</string> - <string name="no_recent_tasks" msgid="9063946524312275906">"Aucune application récente"</string> + <string name="no_recent_tasks" msgid="9063946524312275906">"Aucune appli récente"</string> <string name="global_actions" product="tablet" msgid="4412132498517933867">"Options de la tablette"</string> <string name="global_actions" product="tv" msgid="3871763739487450369">"Options d\'Android TV"</string> <string name="global_actions" product="default" msgid="6410072189971495460">"Options du téléphone"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Assist. vocale"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Verrouillage"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nouvelle notification"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Clavier physique"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sécurité"</string> @@ -923,7 +913,7 @@ </string-array> <string name="phoneTypeCustom" msgid="5120365721260686814">"Personnaliser"</string> <string name="phoneTypeHome" msgid="3880132427643623588">"Domicile"</string> - <string name="phoneTypeMobile" msgid="1178852541462086735">"Mobile"</string> + <string name="phoneTypeMobile" msgid="1178852541462086735">"Cellulaire"</string> <string name="phoneTypeWork" msgid="6604967163358864607">"Travail"</string> <string name="phoneTypeFaxWork" msgid="6757519896109439123">"Téléc. travail"</string> <string name="phoneTypeFaxHome" msgid="6678559953115904345">"Téléc. domicile"</string> @@ -1250,7 +1240,7 @@ <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"Enregistrer l\'image avec %1$s"</string> <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Enregistrer l\'image"</string> <string name="alwaysUse" msgid="3153558199076112903">"Utiliser cette application par défaut pour cette action"</string> - <string name="use_a_different_app" msgid="4987790276170972776">"Utiliser une application différente"</string> + <string name="use_a_different_app" msgid="4987790276170972776">"Utiliser une appli différente"</string> <string name="clearDefaultHintMsg" msgid="1325866337702524936">"Pour supprimer les valeurs par défaut, accédez à Paramètres système > Applications > Téléchargements."</string> <string name="chooseActivity" msgid="8563390197659779956">"Sélectionnez une action"</string> <string name="chooseUsbActivity" msgid="2096269989990986612">"Sélectionnez une application pour le périphérique de stockage USB"</string> @@ -2157,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notification d\'information du mode Routine"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Économiseur de pile activé"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Réduction de l\'utilisation de la pile pour en prolonger l\'autonomie"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"La fonctionnalité Économiseur de pile est activée"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"La fonctionnalité Économiseur de pile est activée pour prolonger l\'autonomie de la pile"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Économiseur de pile"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Le mode Économiseur de pile est désactivé"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Le téléphone est suffisamment chargé. Ces fonctionnalités ne sont plus restreintes."</string> @@ -2231,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Passer à l\'application professionnelle?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Votre organisation vous autorise à passer des appels uniquement à partir d\'applications professionnelles"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Votre organisation vous autorise à envoyer des messages uniquement à partir d\'applications professionnelles"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Vous ne pouvez passer des appels téléphoniques qu\'à partir de votre application Téléphone personnelle. Les appels passés à l\'aide de cette dernière seront ajoutés à votre historique personnel des appels."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Vous ne pouvez envoyer des messages texte qu\'à partir de votre appli Messages personnelle."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Utiliser le navigateur du profil personnel"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Utiliser le navigateur du profil professionnel"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Appeler"</string> @@ -2414,7 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Professionnel 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Commun"</string> - <string name="redacted_notification_message" msgid="1520587845842228816">"Le contenu confidentiel des notifications est masqué"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil professionnel"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espace privé"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Commun"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Le contenu confidentiel de la notification est masqué"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Le contenu de l\'application est masqué du Partage d\'écran par mesure de sécurité"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Connecté au satellite automatiquement"</string> @@ -2422,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configurer le Déverrouillage par empreinte digitale à nouveau"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ne fonctionnait pas bien et a été supprimée. Configurez-le à nouveau pour déverrouiller votre téléphone avec l\'empreinte digitale."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> et <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ne fonctionnaient pas bien et ont été supprimées. Configurez-les à nouveau pour déverrouiller votre téléphone avec votre empreinte digitale."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Configurer le Déverrouillage par reconnaissance faciale à nouveau"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Votre modèle facial ne fonctionnait pas bien et a été supprimé. Configurez-le à nouveau pour déverrouiller votre téléphone avec votre visage."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configurer"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Plus tard"</string> </resources> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 4e5154e43fae..69d42194a95e 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : <xliff:g id="DIALING_NUMBER">{1}</xliff:g> au bout de <xliff:g id="TIME_DELAY">{2}</xliff:g> secondes"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g> : non transféré"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sécurité du réseau mobile"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Chiffrement, notifications pour les réseaux non chiffrés"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID de l\'appareil accessible"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"À <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, un réseau à proximité a enregistré l\'identifiant unique de votre appareil (IMSI ou code IMEI) lors de l\'utilisation de votre carte SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"À <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, un réseau à proximité a enregistré l\'identifiant unique de votre appareil (IMSI ou code IMEI) lors de l\'utilisation de votre carte SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nCela signifie que votre localisation, votre activité ou votre identité ont été enregistrées. Il s\'agit d\'une pratique courante, mais qui peut poser problème aux personnes soucieuses du respect de leur confidentialité."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connecté au 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 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 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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Code de service terminé"</string> <string name="fcError" msgid="5325116502080221346">"Problème de connexion ou code de service non valide"</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Assistance vocale"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Verrouiller"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nouvelle notification"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Clavier physique"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sécurité"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Prendre une capture d\'écran"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Peut prendre des captures d\'écran."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Aperçu, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"ignorer"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"Désactivation ou modification de la barre d\'état"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permet à l\'application de désactiver la barre d\'état, ou d\'ajouter et de supprimer des icônes système."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"remplacer la barre d\'état"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notification d\'information du mode Routine"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Économiseur de batterie activé"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Réduction de l\'utilisation de la batterie pour prolonger son autonomie"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Économiseur de batterie activé"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"L\'économiseur de batterie est activé pour prolonger l\'autonomie de la batterie"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Économiseur de batterie"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Économiseur de batterie désactivé"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Téléphone suffisamment chargé. Les fonctionnalités ne sont plus restreintes."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Passer à une appli professionnelle ?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Votre organisation ne vous autorise à passer des appels que depuis des applis professionnelles"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Votre organisation ne vous autorise à envoyer des messages que depuis des applis professionnelles"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Vous ne pouvez passer des appels téléphoniques qu\'à partir de votre application personnelle Téléphone. Les appels passés avec cette appli seront ajoutés à votre historique d\'appels personnel."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Vous ne pouvez envoyer des SMS qu\'à partir de votre application personnelle Messages."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Utiliser le navigateur personnel"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Utiliser le navigateur professionnel"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Appeler"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Professionnel 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Commun"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil professionnel"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espace privé"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Commun"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Le contenu sensible de la notification a été masqué"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Le contenu de l\'appli est masqué lors du partage d\'écran pour des raisons de sécurité"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Connecté automatiquement au réseau satellite"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Reconfigurer le déverrouillage par empreinte digitale"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ne fonctionnait pas correctement et a été supprimée. Configurez-la à nouveau pour déverrouiller votre téléphone à l\'aide votre empreinte digitale."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> et <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ne fonctionnaient pas correctement et ont été supprimées. Configurez-les à nouveau pour déverrouiller votre téléphone à l\'aide de votre empreinte digitale."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Reconfigurer le déverrouillage par reconnaissance faciale"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Votre empreinte faciale ne fonctionnait pas correctement et a été supprimée. Configurez-la à nouveau pour déverrouiller votre téléphone à l\'aide votre visage."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configuration"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Pas maintenant"</string> </resources> diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml index 2187fedb73b8..dfa4fe3d75b2 100644 --- a/core/res/res/values-gl/strings.xml +++ b/core/res/res/values-gl/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> tras <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: non desviada"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: non reenviada"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Seguranza das redes de telefonía móbil"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encriptación, notificacións para redes non encriptadas"</string> + <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">"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">"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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Máis información"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendido"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de función completo"</string> <string name="fcError" msgid="5325116502080221346">"Problema de conexión ou código de función non válido"</string> <string name="httpErrorOk" msgid="6206751415788256357">"Aceptar"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Asistente voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloquear"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificación nova"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Seguranza"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Facer captura de pantalla"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pode facer capturas de pantalla."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Vista previa, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"pechar"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"desactivar ou modificar a barra de estado"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite á aplicación desactivar a barra de estado ou engadir e quitar as iconas do sistema."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"actuar como a barra de estado"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificación da información do modo de rutina"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Activouse Aforro de batería"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Estase limitando o uso da batería para aumentar a súa duración"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"A función Aforro de batería está activada"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Activouse a función Aforro de batería para prolongar a duración"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Aforro de batería"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Desactivouse a función Aforro de batería."</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"O teléfono non ten suficiente batería. Xa non se restrinxirán as funcións."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Queres cambiar á aplicación do traballo?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"A túa organización só che permite chamar desde aplicacións do traballo"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"A túa organización só che permite enviar mensaxes desde aplicacións do traballo"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Só podes facer chamadas telefónicas desde a aplicación Teléfono persoal. Estas chamadas engadiranse ao teu historial de chamadas."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Só podes enviar SMS desde a aplicación Mensaxes persoal."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Utilizar navegador persoal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Utilizar navegador de traballo"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Chamar"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Traballo 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Proba"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Compartido"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de traballo"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espazo privado"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clonado"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Compartido"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Contido confidencial da notificación oculto"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Por motivos de seguranza, ocultouse o contido da aplicación para que no se mostre na pantalla compartida"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conexión automática ao satélite"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configura de novo o desbloqueo dactilar"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A <xliff:g id="FINGERPRINT">%s</xliff:g> non funcionaba correctamente, polo que se eliminou. Configúraa de novo para desbloquear o teléfono usando a impresión dixital."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"As impresións dixitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> non funcionaban correctamente, polo que se eliminaron. Configúraas de novo para desbloquear o teléfono usando a impresión dixital."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Configura de novo o desbloqueo facial"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"O teu modelo facial non funcionaba correctamente, polo que se eliminou. Configúrao de novo para desbloquear o teléfono usando a cara."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configurar"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Agora non"</string> </resources> diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml index b36b17c95170..8a84d25ee961 100644 --- a/core/res/res/values-gu/strings.xml +++ b/core/res/res/values-gu/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="TIME_DELAY">{2}</xliff:g> સેકન્ડ પછી <xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> પર, તમારા <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="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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"મોબાઇલ નેટવર્ક સુરક્ષા સંબંધી સેટિંગ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"વધુ જાણો"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"સમજાઈ ગયું"</string> <string name="fcComplete" msgid="1080909484660507044">"સુવિધા કોડ પૂર્ણ."</string> <string name="fcError" msgid="5325116502080221346">"કનેક્શન સમસ્યા અથવા અમાન્ય સુવિધા કોડ."</string> <string name="httpErrorOk" msgid="6206751415788256357">"ઓકે"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"વૉઇસ સહાય"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"લૉકડાઉન"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"નવું નોટિફિકેશન"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ભૌતિક કીબોર્ડ"</string> <string name="notification_channel_security" msgid="8516754650348238057">"સુરક્ષા"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"સ્ક્રીનશૉટ લો"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"ડિસ્પ્લેનો સ્ક્રીનશૉટ લઈ શકે છે."</string> <string name="dream_preview_title" msgid="5570751491996100804">"પ્રીવ્યૂ, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"છોડી દો"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"સ્ટેટસ બારને અક્ષમ કરો અથવા તેમાં ફેરફાર કરો"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"ઍપ્લિકેશનને સ્ટેટસ બાર અક્ષમ કરવાની અથવા સિસ્ટમ આયકન્સ ઉમેરવા અને દૂર કરવાની મંજૂરી આપે છે."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"સ્ટેટસ બારમાં બતાવો"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <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> <string name="miniresolver_call" msgid="6386870060423480765">"કૉલ કરો"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"ઑફિસ 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"પરીક્ષણ કરો"</string> <string name="profile_label_communal" msgid="8743921499944800427">"કૉમ્યુનલ"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ઑફિસની પ્રોફાઇલ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ખાનગી સ્પેસ"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ક્લોન"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"કૉમ્યુનલ"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"સંવેદનશીલ માહિતીવાળા નોટિફિકેશનનું કન્ટેન્ટ છુપાવ્યું"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"સુરક્ષા માટે સ્ક્રીન શેર કરતી વખતે ઍપનું કન્ટેન્ટ છુપાવેલું છે"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ફિંગરપ્રિન્ટ અનલૉક સુવિધાનું ફરી સેટઅપ કરો"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> બરાબર કામ કરતી ન હતી અને તેને ડિલીટ કરવામાં આવી હતી. તમારા ફોનને ફિંગરપ્રિન્ટ વડે અનલૉક કરવા માટે, તેનું ફરીથી સેટઅપ કરો."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> અને <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> બરાબર કામ કરતી ન હતી અને તેને ડિલીટ કરવામાં આવી હતી. તમારા ફોનને તમારી ફિંગરપ્રિન્ટ વડે અનલૉક કરવા માટે, તેનું ફરીથી સેટઅપ કરો."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ફેસ અનલૉક સુવિધાનું ફરી સેટઅપ કરો"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"તમારા ચહેરાનું મૉડલ બરાબર કામ કરતું ન હતું અને તેને ડિલીટ કરવામાં આવ્યું હતું. તમારા ફોનને ચહેરા વડે અનલૉક કરવા માટે, તેનું ફરીથી સેટઅપ કરો."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"સેટઅપ કરો"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"હમણાં નહીં"</string> </resources> diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml index 40470cd72015..e43491294ce2 100644 --- a/core/res/res/values-hi/strings.xml +++ b/core/res/res/values-hi/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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_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="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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ज़्यादा जानें"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ठीक है"</string> <string name="fcComplete" msgid="1080909484660507044">"सुविधा कोड पूरा हुआ."</string> <string name="fcError" msgid="5325116502080221346">"कनेक्शन समस्या या अमान्य सुविधा कोड."</string> <string name="httpErrorOk" msgid="6206751415788256357">"ठीक है"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"आवाज़ से डिवाइस का इस्तेमाल"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"लॉकडाउन"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"नई सूचना"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"सामान्य कीबोर्ड"</string> <string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string> @@ -678,7 +668,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> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"सिर्फ़ अपने व्यक्तिगत Phone ऐप्लिकेशन से कॉल किए जा सकते हैं. व्यक्तिगत Phone से किए गए कॉल, व्यक्तिगत कॉल इतिहास में सेव होते हैं."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"सिर्फ़ व्यक्तिगत Messages ऐप्लिकेशन से एसएमएस मैसेज भेजे जा सकते हैं."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"निजी ब्राउज़र का इस्तेमाल करें"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"ऑफ़िस के काम से जुड़े ब्राउज़र का इस्तेमाल करें"</string> <string name="miniresolver_call" msgid="6386870060423480765">"कॉल करें"</string> @@ -2406,13 +2396,17 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"कीबोर्ड का लेआउट <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… पर सेट कर दिया गया है. इसे बदलने के लिए टैप करें."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"फ़िज़िकल कीबोर्ड कॉन्फ़िगर किए गए"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"कीबोर्ड देखने के लिए टैप करें"</string> - <string name="profile_label_private" msgid="6463418670715290696">"निजी"</string> + <string name="profile_label_private" msgid="6463418670715290696">"प्राइवेट"</string> <string name="profile_label_clone" msgid="769106052210954285">"क्लोन"</string> <string name="profile_label_work" msgid="3495359133038584618">"ऑफ़िस"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"ऑफ़िस 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"ऑफ़िस 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"टेस्ट"</string> <string name="profile_label_communal" msgid="8743921499944800427">"कम्यूनिटी"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"वर्क प्रोफ़ाइल"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"प्राइवेट स्पेस"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"क्लोन"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"कम्यूनिटी"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"संवेदनशील जानकारी वाली सूचना का कॉन्टेंट छिपा है"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"स्क्रीन शेयर करने के दौरान सुरक्षा के लिए, ऐप्लिकेशन का कॉन्टेंट छिपाया गया"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"फ़िंगरप्रिंट अनलॉक की सुविधा दोबारा सेट अप करें"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"अच्छे से काम न करने की वजह से <xliff:g id="FINGERPRINT">%s</xliff:g> को मिटा दिया गया. फ़िंगरप्रिंट की मदद से फ़ोन अनलॉक करने के लिए, फ़िंगरप्रिंट अनलॉक की सुविधा को दोबारा सेट अप करें."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"अच्छे से काम न करने की वजह से, <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> और <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> को मिटा दिया गया. फ़िंगरप्रिंट की मदद से फ़ोन अनलॉक करने के लिए, फ़िंगरप्रिंट अनलॉक की सुविधा दोबारा सेट अप करें."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"फ़ेस अनलॉक की सुविधा को दोबारा सेट अप करें"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"अच्छे से काम न करने की वजह से, चेहरे का मॉडल मिटा दिया गया. फ़ेस अनलॉक की सुविधा की मदद से फ़ोन अनलॉक करने के लिए, इस सुविधा को दोबारा सेट अप करें."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"सेट अप करें"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"अभी नहीं"</string> </resources> diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index 64f1368d991a..0bd8be3b4472 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> nakon <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nije proslijeđeno"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nije proslijeđeno"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sigurnost mobilne mreže"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Šifriranje i obavijesti za nešifrirane mreže"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Pristupljeno je ID-ju uređaja"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"U <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> mreža u blizini zabilježila je jedinstveni ID (IMSI ili IMEI) vašeg uređaja tijekom upotrebe SIM-a iz mreže <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"U <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> mreža u blizini zabilježila je jedinstveni ID (IMSI ili IMEI) vašeg uređaja tijekom upotrebe SIM-a iz mreže <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nTo znači da je vaša lokacija, aktivnost ili identitet zapisan. To je uobičajena praksa, no može predstavljati problem za osobe koje brine privatnost."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Telefon je povezan sa šifriranom mrežom <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Veza SIM-a s mrežom <xliff:g id="NETWORK_NAME">%1$s</xliff:g> sada je sigurnija"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Uređaj je povezan s nešifriranom mrežom"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pozivi, poruke i podaci trenutačno su ranjiviji dok se upotrebljava SIM iz mreže <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pozivi, poruke i podaci trenutačno su ranjiviji dok se upotrebljava SIM iz mreže <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKada se vaša veza ponovno šifrira, dobit ćete još jednu obavijest."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Postavke sigurnosti mobilne mreže"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saznajte više"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Shvaćam"</string> <string name="fcComplete" msgid="1080909484660507044">"Kôd značajke je potpun."</string> <string name="fcError" msgid="5325116502080221346">"Problem s vezom ili nevažeći kôd značajke."</string> <string name="httpErrorOk" msgid="6206751415788256357">"U redu"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Glasovna pomoć"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zaključaj"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nova obavijest"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizička tipkovnica"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sigurnost"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Snimi zaslon"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Možete napraviti snimku zaslona."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Pregled, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"odbaci"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"onemogućavanje ili izmjena trake statusa"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Aplikaciji omogućuje onemogućavanje trake statusa ili dodavanje i uklanjanje sistemskih ikona."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"biti traka statusa"</string> @@ -1225,7 +1214,7 @@ <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{Jedna zvjezdica od {max}}one{# zvjezdica od {max}}few{# zvjezdice od {max}}other{# zvjezdica od {max}}}"</string> <string name="in_progress" msgid="2149208189184319441">"u tijeku"</string> <string name="whichApplication" msgid="5432266899591255759">"Radnju dovrši pomoću stavke"</string> - <string name="whichApplicationNamed" msgid="6969946041713975681">"Dovršavanje radnje pomoću aplikacije %1$s"</string> + <string name="whichApplicationNamed" msgid="6969946041713975681">"Dovršite radnju putem aplikacije %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Dovrši radnju"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Otvaranje pomoću aplikacije"</string> <string name="whichViewApplicationNamed" msgid="415164730629690105">"Otvaranje pomoću aplikacije %1$s"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Obavještavanje o informacijama u Rutinskom načinu rada"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Uključena je štednja baterije"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Smanjuje se potrošnja baterije radi produženja njezinog trajanja"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Štednja baterije je uključena"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Štednja baterije je uključena da bi se produljilo trajanje baterije"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Štednja baterije"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Isključena je Štednja baterije"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija mobilnog telefona dovoljno je napunjena. Značajke više nisu ograničene."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Želite li prebaciti na poslovnu aplikaciju?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Vaša organizacija dopušta upućivanje poziva samo iz poslovnih aplikacija"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Vaša organizacija dopušta slanje poruka samo iz poslovnih aplikacija"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Pozive možete upućivati samo iz svoje privatne aplikacije Telefon. Pozivi koji se upućuju iz privatne aplikacije Telefon dodat će se u vašu privatnu povijest poziva."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS poruke možete slati samo iz svoje privatne aplikacije Poruke."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Koristi osobni preglednik"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Koristi poslovni preglednik"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Nazovi"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Posao 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Zajedničko"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Radni profil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privatni prostor"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Zajedničko"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Skriven je osjetljiv sadržaj obavijesti"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Sadržaj aplikacije sakriven je od dijeljenja zaslona radi sigurnosti"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatski povezano sa satelitom"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ponovno postavite otključavanje otiskom prsta"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Otisak prsta <xliff:g id="FINGERPRINT">%s</xliff:g> nije dobro funkcionirao i izbrisan je. Ponovno ga postavite da biste otključali telefon otiskom prsta."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Otisci prstiju <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nisu dobro funkcionirali i izbrisani su. Ponovno ih postavite da biste otključali telefon otiskom prsta."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Ponovno postavite otključavanje licem"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Model vašeg lica nije dobro funkcionirao i izbrisan je. Ponovno ga postavite da biste otključali telefon licem."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Postavi"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ne sad"</string> </resources> diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml index b4ac6752c398..aeebbdc22aac 100644 --- a/core/res/res/values-hu/strings.xml +++ b/core/res/res/values-hu/strings.xml @@ -153,31 +153,19 @@ <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> másodperc után"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nincs átirányítva"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nincs átirányítva"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobilhálózati biztonság"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Titkosítás, értesítés titkosítás nélküli hálózatok esetén"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Hozzáférés az eszközazonosítóhoz"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"A következő időpontban egy közeli hálózat észlelte az Ön eszközének egyedi azonosítóját (IMSI vagy IMEI), miközben az eszköz az Ön <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM-jét használta: <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"A következő időpontban egy közeli hálózat rögzítette az Ön eszközének egyedi azonosítóját (IMSI vagy IMEI), miközben az eszköz az Ön <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>-SIM-jét használta: <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>.\n\nEz azt jelenti, hogy az Ön tartózkodási helye, tevékenysége és személyazonossága naplózásra került. Ez bevett gyakorlat, de problémát jelenthet az adatvédelem miatt aggódó személyek számára."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"A következő titkosított hálózathoz csatlakozik: <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"A(z) <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM-kapcsolata mostantól biztonságosabb"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Nem titkosított hálózathoz csatlakozik"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"A hívások, az SMS-ek és az adatforgalom jelenleg sebezhetőbbek, amíg <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM-jét használja."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"A hívások, az SMS-ek és az adatforgalom jelenleg sebezhetőbbek, amíg <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-SIM-jét használja.\n\nAmint hálózata újra titkosított lesz, újabb értesítést fog kapni."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"A mobilhálózati biztonság beállításai"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"További információ"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Értem"</string> <string name="fcComplete" msgid="1080909484660507044">"A funkciókód kész."</string> <string name="fcError" msgid="5325116502080221346">"Kapcsolódási probléma vagy érvénytelen funkciókód."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Hangsegéd"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zárolás"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Új értesítés"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizikai billentyűzet"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Biztonság"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Képernyőkép készítése"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Készíthet képernyőképet a kijelzőről."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Előnézet, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"elvetés"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"állapotsor kikapcsolása vagy módosítása"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Lehetővé teszi az alkalmazás számára az állapotsor kikapcsolását, illetve rendszerikonok hozzáadását és eltávolítását."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"az állapotsor szerepének átvétele"</string> @@ -2010,7 +1999,7 @@ <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Vészhelyzet"</string> <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Állítson be képernyőzárat"</string> <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Képernyőzár beállítása"</string> - <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"A magánterület használatához állítson be képernyőzárat"</string> + <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"A privát terület használatához állítson be képernyőzárat"</string> <string name="app_blocked_title" msgid="7353262160455028160">"Az alkalmazás nem hozzáférhető"</string> <string name="app_blocked_message" msgid="542972921087873023">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> jelenleg nem hozzáférhető."</string> <string name="app_streaming_blocked_title" msgid="6090945835898766139">"A(z) <xliff:g id="ACTIVITY">%1$s</xliff:g> nem áll rendelkezése"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Információs értesítés a rutinmódról"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Akkumulátorkímélő mód bekapcsolva"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Akkuhasználat csökkentése a hosszabb akkumulátor-élettartam érdekében"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Akkumulátorkímélő mód bekapcsolva"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Az akkumulátor-üzemidő meghosszabbítása érdekében bekapcsolódott az Akkumulátorkímélő mód"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akkumulátorkímélő mód"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akkumulátorkímélő mód kikapcsolva"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"A telefon töltöttsége elegendő. A funkciók használata már nincs korlátozva."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Átvált a munkahelyi alkalmazásra?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Szervezete csak munkahelyi alkalmazásokból engedélyezi a hívásindítást"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Szervezete csak munkahelyi alkalmazásokból engedélyezi az üzenetküldést"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Csak az Ön személyes Telefon alkalmazásából indíthat hívásokat. A személyes Telefon alkalmazással indított hívásokat hozzáadja a rendszer az Ön személyes híváslistájához."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Csak az Ön személyes Messages alkalmazásából küldhet SMS-eket."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Személyes böngésző használata"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Munkahelyi böngésző használata"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Hívás"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"3. munkahelyi"</string> <string name="profile_label_test" msgid="9168641926186071947">"Teszt"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Közös"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Munkaprofil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privát terület"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klón"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Közös"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Bizalmas értesítéstartalom elrejtve"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"A biztonság érdekében a képernyőmegosztástól elrejtett alkalmazástartalom"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatikusan csatlakozva a műholdhoz"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"A Feloldás ujjlenyomattal funkció újbóli beállítása"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A(z) <xliff:g id="FINGERPRINT">%s</xliff:g> nem működött megfelelően, ezért törölve lett. Állítsa be újra, hogy feloldhassa a telefonját az ujjlenyomata segítségével."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"A(z) <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> és a(z) <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nem működtek megfelelően, ezért törölve lettek. Állítsa be őket újra, hogy feloldhassa a telefonját az ujjlenyomata segítségével."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Állítsa be újra az Arcalapú feloldást"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Az arcmodellje nem működött megfelelően, ezért törölve lett. Állítsa be újra, hogy feloldhassa a telefonját az arca segítségével."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Beállítás"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Most nem"</string> </resources> diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml index 609810aed9c9..a0e0bd218a18 100644 --- a/core/res/res/values-hy/strings.xml +++ b/core/res/res/values-hy/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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>-ի ձեր 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> + <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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Ցանցային անվտանգության կարգավորումներ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Իմանալ ավելին"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Եղավ"</string> <string name="fcComplete" msgid="1080909484660507044">"Հատկության կոդը ամբողջական է:"</string> <string name="fcError" msgid="5325116502080221346">"Կապի խնդիր կամ անվավեր գործառույթի կոդ:"</string> <string name="httpErrorOk" msgid="6206751415788256357">"Եղավ"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ձայնային օգնութ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Արգելափակում"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Նոր ծանուցում"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Ֆիզիկական ստեղնաշար"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Անվտանգություն"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Սքրինշոթի ստեղծում"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Կարող է ստեղծել էկրանի սքրինշոթ։"</string> <string name="dream_preview_title" msgid="5570751491996100804">"Նախադիտում, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"փակել"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"անջատել կամ փոփոխել կարգավիճակի գոտին"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Թույլ է տալիս հավելվածին անջատել կարգավիճակի գոտին կամ ավելացնել ու հեռացնել համակարգի պատկերակները:"</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"լինել կարգավիճակի գոտի"</string> @@ -1224,7 +1213,7 @@ <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{Մեկ աստղ՝ {max}-ից}one{# աստղ՝ {max}-ից}other{# աստղ՝ {max}-ից}}"</string> <string name="in_progress" msgid="2149208189184319441">"ընթացքում է"</string> <string name="whichApplication" msgid="5432266899591255759">"Ավարտել գործողությունը` օգտագործելով"</string> - <string name="whichApplicationNamed" msgid="6969946041713975681">"Եզրափակել գործողությունը՝ օգտագործելով %1$s"</string> + <string name="whichApplicationNamed" msgid="6969946041713975681">"Կատարել՝ օգտագործելով %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Ավարտել գործողությունը"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Բացել հետևյալ ծրագրով՝"</string> <string name="whichViewApplicationNamed" msgid="415164730629690105">"Բացել հավելվածով՝ %1$s"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Դուք կարող եք զանգահարել միայն ձեր անձնական «Հեռախոս» հավելվածից։ Անձնական «Հեռախոս» հավելվածով կատարված զանգերը կավելացվեն ձեր անձնական զանգերի պատմության մեջ։"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Դուք կարող եք SMS հաղորդագրություններ ուղարկել միայն ձեր անձնական Messages հավելվածից։"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Օգտագործել անձնական դիտարկիչը"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Օգտագործել աշխատանքային դիտարկիչը"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Զանգել"</string> @@ -2407,15 +2396,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Ստեղնաշարի համար կարգավորված են <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g> դասավորությունները։ Հպեք փոխելու համար։"</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Ֆիզիկական ստեղնաշարերը կարգավորված են"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Հպեք՝ ստեղնաշարերը դիտելու համար"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Անձնական"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Մասնավոր"</string> <string name="profile_label_clone" msgid="769106052210954285">"Կլոն"</string> <string name="profile_label_work" msgid="3495359133038584618">"Աշխատանքային"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Աշխատանքային 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Աշխատանքային 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Փորձնական"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Ընդհանուր"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Աշխատանքային պրոֆիլ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Մասնավոր տարածք"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Կլոն"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Ընդհանուր"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Ծանուցման զգայուն բովանդակությունը թաքցված է"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Անվտանգության նկատառումներից ելնելով՝ հավելվածի բովանդակությունը թաքցվել է էկրանի ցուցադրումից"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Ավտոմատ միացել է արբանյակին"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Նորից կարգավորեք մատնահետքով ապակողպումը"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"«<xliff:g id="FINGERPRINT">%s</xliff:g>» մատնահետքը հեռացվել է, քանի որ լավ չէր աշխատում։ Նորից կարգավորեք այն՝ ձեր հեռախոսը մատնահետքով ապակողպելու համար։"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"«<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>» և «<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>» մատնահետքերը հեռացվել են, քանի որ լավ չէին աշխատում։ Նորից կարգավորեք դրանք՝ ձեր հեռախոսը մատնահետքով ապակողպելու համար։"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Նորից կարգավորեք դեմքով ապակողպումը"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Ձեր դեմքի նմուշը հեռացվել է, քանի որ լավ չէր աշխատում։ Նորից կարգավորեք այն՝ ձեր հեռախոսը դեմքով ապակողպելու համար։"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Կարգավորել"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ոչ հիմա"</string> </resources> diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml index 1956c92b84ac..fb6180b0d126 100644 --- a/core/res/res/values-in/strings.xml +++ b/core/res/res/values-in/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> setelah <xliff:g id="TIME_DELAY">{2}</xliff:g> detik"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak diteruskan"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak diteruskan"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Keamanan jaringan seluler"</string> + <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, 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 kembali menerima notifikasi."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Setelan keamanan jaringan seluler"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Pelajari lebih lanjut"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Oke"</string> <string name="fcComplete" msgid="1080909484660507044">"Kode fitur selesai."</string> <string name="fcError" msgid="5325116502080221346">"Masalah sambungan atau kode fitur tidak valid."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Oke"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Bantuan Suara"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Kunci total"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Notifikasi baru"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Keyboard fisik"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Keamanan"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ambil screenshot"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Dapat mengambil screenshot tampilan."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Pratinjau, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"tutup"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"nonaktifkan atau ubah bilah status"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Mengizinkan apl menonaktifkan bilah status atau menambah dan menghapus ikon sistem."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"jadikan bilah status"</string> @@ -652,7 +641,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> @@ -1250,7 +1239,7 @@ <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"Jepret gambar dengan %1$s"</string> <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"Jepret gambar"</string> <string name="alwaysUse" msgid="3153558199076112903">"Gunakan secara default untuk tindakan ini."</string> - <string name="use_a_different_app" msgid="4987790276170972776">"Gunakan aplikasi yang berbeda"</string> + <string name="use_a_different_app" msgid="4987790276170972776">"Gunakan aplikasi lain"</string> <string name="clearDefaultHintMsg" msgid="1325866337702524936">"Menghapus default di Setelan sistem > Apl > Terdownload."</string> <string name="chooseActivity" msgid="8563390197659779956">"Pilih tindakan"</string> <string name="chooseUsbActivity" msgid="2096269989990986612">"Pilih apl untuk perangkat USB"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notifikasi info Mode Rutinitas"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Penghemat Baterai diaktifkan"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Mengurangi penggunaan baterai untuk memperpanjang masa pakai baterai"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Penghemat Baterai aktif"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Penghemat Baterai diaktifkan untuk memperpanjang daya tahan baterai"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Penghemat Baterai"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Penghemat Baterai dinonaktifkan"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterai ponsel cukup terisi. Fitur tidak lagi dibatasi."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Beralih ke aplikasi kerja?"</string> <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <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 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> @@ -2407,13 +2396,17 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Tata letak keyboard disetel ke <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Ketuk untuk mengubah."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Keyboard fisik telah dikonfigurasi"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Ketuk untuk melihat keyboard"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Pribadi"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Privasi"</string> <string name="profile_label_clone" msgid="769106052210954285">"Clone"</string> <string name="profile_label_work" msgid="3495359133038584618">"Kerja"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Kerja 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Kerja 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Pengujian"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Umum"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil kerja"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ruang privasi"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Umum"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Konten notifikasi sensitif disembunyikan"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Konten aplikasi disembunyikan dari berbagi layar untuk alasan keamanan"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Siapkan Buka dengan Sidik Jari lagi"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> tidak berfungsi dengan baik dan telah dihapus. Siapkan lagi untuk membuka kunci ponsel Anda dengan sidik jari."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> dan <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> tidak berfungsi dengan baik dan telah dihapus. Siapkan lagi untuk membuka kunci ponsel Anda dengan sidik jari."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Siapkan Buka dengan Wajah lagi"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Model wajah Anda tidak berfungsi dengan baik dan telah dihapus. Siapkan lagi untuk membuka kunci ponsel Anda dengan wajah."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Penyiapan"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Lain kali"</string> </resources> diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml index 2a02c0c966a2..3f96dae37c8d 100644 --- a/core/res/res/values-is/strings.xml +++ b/core/res/res/values-is/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> eftir <xliff:g id="TIME_DELAY">{2}</xliff:g> sekúndur"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ekki áframsent"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ekki áframsent"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Öryggi farsímakerfis"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Dulkóðun og tilkynningar um ódulkóðuð netkerfi"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Auðkenni tækis opnað"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Klukkan <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> skráði nálægt netkerfi einkvæmt auðkenni tækisins þíns (IMSI eða IMEI) með því að nota tengingu SIM-kortsins þíns við <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Klukkan <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> skráði nálægt netkerfi einkvæmt auðkenni tækisins þíns (IMSI eða IMEI) með því að nota tengingu SIM-kortsins þíns við <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nÞað þýðir að staðsetningin þín, virkni eða auðkenni hefur verið skráð. Þetta er algengt verklag en kann að valda fólki áhyggjum sem er annt um persónuvernd sína."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Þú ert með tengingu við dulkóðaða netkerfið „<xliff:g id="NETWORK_NAME">%1$s</xliff:g>“"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Tenging SIM-kortsins við <xliff:g id="NETWORK_NAME">%1$s</xliff:g> er nú öruggari"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Þú ert með tengingu við ódulkóðað netkerfi"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Símtöl, skilaboð og gögn eru nú berskjaldaðri þegar þú notar SIM-kortið sem er tengt við <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Símtöl, skilaboð og gögn eru nú berskjaldaðri þegar þú notar SIM-kortið sem er tengt við <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nÞú færð aðra tilkynningu þegar tengingin verður dulkóðuð á ný."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Öryggisstillingar farsímakerfis"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Nánar"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Ég skil"</string> <string name="fcComplete" msgid="1080909484660507044">"Kóðaskipun framkvæmd."</string> <string name="fcError" msgid="5325116502080221346">"Vandamál með tengingu eða ógild kóðaskipun."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Í lagi"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Raddaðstoð"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Læsing"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Ný tilkynning"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Vélbúnaðarlyklaborð"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Öryggi"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Taka skjámynd"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Getur tekið skjámynd af skjánum."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Forskoðun, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"hunsa"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"slökkva á eða breyta stöðustiku"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Leyfir forriti að slökkva á stöðustikunni eða bæta við og fjarlægja kerfistákn."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"vera stöðustikan"</string> @@ -1018,8 +1007,8 @@ <string name="lockscreen_missing_sim_message" product="tablet" msgid="3986843848305639161">"Það er ekkert SIM-kort í spjaldtölvunni."</string> <string name="lockscreen_missing_sim_message" product="tv" msgid="3903140876952198273">"Það er ekkert SIM-kort í Android TV-tækinu."</string> <string name="lockscreen_missing_sim_message" product="default" msgid="6184187634180854181">"Það er ekkert SIM-kort í símanum."</string> - <string name="lockscreen_missing_sim_instructions" msgid="5823469004536805423">"Bæta við SIM-korti."</string> - <string name="lockscreen_missing_sim_instructions_long" msgid="4403843937236648032">"SIM-kort vantar eða er ekki læsilegt. Bæta við SIM-korti."</string> + <string name="lockscreen_missing_sim_instructions" msgid="5823469004536805423">"Bæta SIM-korti við."</string> + <string name="lockscreen_missing_sim_instructions_long" msgid="4403843937236648032">"SIM-kort vantar eða er ekki læsilegt. Bæta SIM-korti við."</string> <string name="lockscreen_permanent_disabled_sim_message_short" msgid="1925200607820809677">"Ónothæft SIM-kort."</string> <string name="lockscreen_permanent_disabled_sim_instructions" msgid="6902979937802238429">"SIM-kortið þitt var gert varanlega óvirkt.\n Hafðu samband við símafyrirtækið þitt til að fá nýtt SIM-kort."</string> <string name="lockscreen_transport_prev_description" msgid="2879469521751181478">"Fyrra lag"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Upplýsingatilkynning aðgerðastillingar"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Kveikt á rafhlöðusparnaði"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Dregur úr rafhlöðunotkun til að auka endingu rafhlöðunnar"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Kveikt er á rafhlöðusparnaði"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Kveikt er á rafhlöðusparnaði til að lengja rafhlöðuendingu"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Rafhlöðusparnaður"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Slökkt á rafhlöðusparnaði"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Síminn er með næga hleðslu. Eiginleikar eru ekki lengur takmarkaðir."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Skipta yfir í vinnuforrit?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Fyrirtækið heimilar þér aðeins að hringja úr vinnuforritum"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Fyrirtækið heimilar þér aðeins að senda skilaboð úr vinnuforritum"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Þú getur eingöngu hringt símtöl úr þínu persónulega símaforriti. Símtölum úr persónulegu símaforriti verður bætt við þinn persónulega símtalaferil."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Þú getur eingöngu sent SMS-skilaboð úr þínu persónulega skilaboðaforriti."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Nota einkavafra"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Nota vinnuvafra"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Hringja"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Vinna 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Prófun"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Sameiginlegt"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Vinnusnið"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Leynirými"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Afrit"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Sameiginlegt"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Viðkvæmt tilkynningaefni falið"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Efni forrits falið í skjádeilingu af öryggisástæðum"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Tengdist sjálfkrafa við gervihnött"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Setja upp fingrafarskenni aftur"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> virkaði illa og var eytt. Settu það upp aftur til að taka símann úr lás með fingrafari."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> virkuðu illa og var eytt. Settu þau upp aftur til að taka símann úr lás með fingrafarinu þínu."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Setja upp andlitskenni aftur"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Andlitslíkanið þitt virkaði illa og var eytt. Settu það upp aftur til að taka símann úr lás með andlitinu."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Setja upp"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ekki núna"</string> </resources> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index b943073ccc26..f22b6531c66e 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> dopo <xliff:g id="TIME_DELAY">{2}</xliff:g> secondi"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: inoltro non effettuato"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: inoltro non effettuato"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Sicurezza di rete mobile"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Crittografia, notifiche per reti non criptate"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"È stato eseguito un accesso all\'ID dispositivo"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Alle ore <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> una rete nelle vicinanze ha registrato l\'ID univoco (IMSI o IMEI) del tuo dispositivo mentre era in uso la tua SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Alle ore <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> una rete nelle vicinanze ha registrato l\'ID univoco (IMSI o IMEI) del tuo dispositivo mentre era in uso la tua SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nQuesto significa che la tua posizione, attività o identità è stata registrata. Si tratta di una prassi comune, ma potrebbe rappresentare un problema per le persone preoccupate riguardo alla privacy."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Connesso alla rete criptata <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"La connessione della SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ora è più sicura"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Connesso a una rete non criptata"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Chiamate, messaggi e dati al momento sono più vulnerabili quando usi la tua SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Chiamate, messaggi e dati al momento sono più vulnerabili quando usi la tua SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nQuando la tua connessione sarà nuovamente criptata, riceverai un\'altra notifica."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Impostazioni della sicurezza di rete mobile"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Scopri di più"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Ok"</string> <string name="fcComplete" msgid="1080909484660507044">"Codice funzione completo."</string> <string name="fcError" msgid="5325116502080221346">"Problema di connessione o codice funzione non valido."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blocco"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nuova notifica"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastiera fisica"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sicurezza"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Acquisire screenshot"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Può acquisire uno screenshot del display."</string> <string name="dream_preview_title" msgid="5570751491996100804">"<xliff:g id="DREAM_NAME">%1$s</xliff:g> in anteprima"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"chiudi"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"disattivazione o modifica della barra di stato"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Consente all\'applicazione di disattivare la barra di stato o di aggiungere e rimuovere icone di sistema."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"ruolo di barra di stato"</string> @@ -1225,7 +1214,7 @@ <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{Una stella su {max}}many{# stelle su {max}}other{# stelle su {max}}}"</string> <string name="in_progress" msgid="2149208189184319441">"In corso"</string> <string name="whichApplication" msgid="5432266899591255759">"Completa l\'azione con"</string> - <string name="whichApplicationNamed" msgid="6969946041713975681">"Completamento azione con %1$s"</string> + <string name="whichApplicationNamed" msgid="6969946041713975681">"Completa l\'azione con %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Completa azione"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Apri con"</string> <string name="whichViewApplicationNamed" msgid="415164730629690105">"Apri con %1$s"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notifica di informazioni sulla modalità Routine"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Risparmio energetico attivato"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Riduzione dell\'utilizzo di batteria per estenderne la durata"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Risparmio energetico attivo"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Risparmio energetico è attivo per aumentare la durata della batteria"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Risparmio energetico"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Risparmio energetico disattivato"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Il telefono ha carica sufficiente. Funzionalità non più limitate."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Vuoi passare all\'app di lavoro?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"La tua organizzazione consente di fare chiamate solo dalle app di lavoro"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"La tua organizzazione consente di inviare messaggi solo dalle app di lavoro"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Puoi effettuare chiamate solo dalla tua app Telefono personale. Le chiamate effettuate con la tua app Telefono personale verranno aggiunte alla tua cronologia chiamate personale."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Puoi inviare messaggi SMS solo dalla tua app Messaggi personale."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Usa il browser personale"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Usa il browser di lavoro"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Chiama"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Lavoro 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Condiviso"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profilo di lavoro"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Spazio privato"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Condiviso"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Contenuti sensibili della notifica nascosti"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Contenuti dell\'app nascosti dalla condivisione schermo per sicurezza"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Connessione automatica al satellite"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Riconfigura lo Sblocco con l\'Impronta"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> non funzionava bene ed è stata eliminata. Riconfigurala per sbloccare lo smartphone con l\'impronta."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> non funzionavano bene e sono state eliminate. Riconfigurale per sbloccare lo smartphone con l\'impronta."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Riconfigura lo Sblocco con il Volto"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Il tuo modello del volto non funzionava bene ed è stato eliminato. Riconfiguralo per sbloccare lo smartphone con il volto."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configura"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Non ora"</string> </resources> diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml index 62ed6fce2379..3a924e1916c0 100644 --- a/core/res/res/values-iw/strings.xml +++ b/core/res/res/values-iw/strings.xml @@ -154,31 +154,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"אבטחת הרשת הסלולרית"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"הצפנה, התראות על רשתות לא מוצפנות"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"בוצעה גישה למזהה המכשיר"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"בשעה <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, רשת קרובה רשמה את המזהה הייחודי של המכשיר שלך (IMSI או IMEI) בזמן השימוש בכרטיס ה-SIM של <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"הבנתי"</string> <string name="fcComplete" msgid="1080909484660507044">"קוד תכונה הושלם."</string> <string name="fcError" msgid="5325116502080221346">"בעיה בחיבור או קוד תכונה לא תקין."</string> <string name="httpErrorOk" msgid="6206751415788256357">"אישור"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"האסיסטנט"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"נעילה"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"התראה חדשה"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"מקלדת פיזית"</string> <string name="notification_channel_security" msgid="8516754650348238057">"אבטחה"</string> @@ -2157,6 +2147,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> + <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> @@ -2231,10 +2223,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"ניתן לבצע שיחות טלפון רק מאפליקציית הטלפון האישית שלך. שיחות שיבוצעו באמצעות הטלפון האישי יתווספו להיסטוריית השיחות האישית שלך."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"אפשר לשלוח הודעות SMS רק מאפליקציית ההודעות האישית שלך."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"בדפדפן האישי"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"בדפדפן של העבודה"</string> <string name="miniresolver_call" msgid="6386870060423480765">"שיחה"</string> @@ -2407,15 +2397,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"פריסת המקלדת מוגדרת ל<xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… אפשר להקיש כדי לשנות את ההגדרה הזו."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"הוגדרו מקלדות פיזיות"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"יש להקיש כדי להציג את המקלדות"</string> - <string name="profile_label_private" msgid="6463418670715290696">"פרטי"</string> + <string name="profile_label_private" msgid="6463418670715290696">"פרופיל פרטי"</string> <string name="profile_label_clone" msgid="769106052210954285">"שכפול"</string> <string name="profile_label_work" msgid="3495359133038584618">"פרופיל עבודה"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"פרופיל עבודה 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"פרופיל עבודה 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"בדיקה"</string> <string name="profile_label_communal" msgid="8743921499944800427">"שיתופי"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"פרופיל העבודה"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"המרחב הפרטי"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"שכפול"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"שיתופי"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"יש תוכן רגיש בהתראה שהוסתר"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"תוכן האפליקציה מוסתר משיתוף המסך מטעמי אבטחה"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"חיבור אוטומטי ללוויין"</string> @@ -2423,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"הגדרה חוזרת של \'ביטול הנעילה בטביעת אצבע\'"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> לא פעלה היטב ולכן היא נמחקה. עליך להגדיר אותה שוב כדי שתהיה לך אפשרות לבטל את הנעילה של הטלפון באמצעות טביעת אצבע."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ו<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> לא פעלו היטב ולכן הן נמחקו. עליך להגדיר אותן שוב כדי שתהיה לך אפשרות לבטל את הנעילה של הטלפון באמצעות טביעת אצבע."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"הגדרה חוזרת של \'פתיחה ע\"י זיהוי הפנים\'"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"התבנית לזיהוי הפנים לא פעלה היטב ולכן היא נמחקה. עליך להגדיר אותה שוב כדי שתהיה לך אפשרות לבטל את הנעילה של הטלפון באמצעות זיהוי הפנים."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"הגדרה"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"לא עכשיו"</string> </resources> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 61eb776fe0ad..e31d79574841 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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>、<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="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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"詳細"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"機能コードが完了しました。"</string> <string name="fcError" msgid="5325116502080221346">"接続エラーまたは無効な機能コードです。"</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"音声アシスト"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"ロックダウン"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"新しい通知"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"物理キーボード"</string> <string name="notification_channel_security" msgid="8516754650348238057">"セキュリティ"</string> @@ -2156,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"ルーティン モード情報の通知"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"バッテリー セーバーが ON になりました"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"バッテリー使用量を減らし、バッテリー駆動時間を延ばします"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"バッテリー セーバーが ON になっています"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"バッテリーを長持ちさせるためにバッテリー セーバーが ON になっています"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"バッテリー セーバー"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"バッテリー セーバーが OFF になりました"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"スマートフォンが十分に充電されました。機能は制限されなくなりました。"</string> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"電話は個人用の電話アプリからのみかけることができます。個人用の電話アプリを使った通話は、個人用の通話履歴に追加されます。"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS メッセージは個人用のメッセージ アプリからのみ送信できます。"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"個人用ブラウザを使用"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"仕事用ブラウザを使用"</string> <string name="miniresolver_call" msgid="6386870060423480765">"通話"</string> @@ -2413,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"仕事用 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"テスト"</string> <string name="profile_label_communal" msgid="8743921499944800427">"共用"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"仕事用プロファイル"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"プライベート スペース"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"複製"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"共用"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"プライベートな通知内容は表示されません"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"セキュリティ上、画面共有ではアプリの内容は非表示となります"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"指紋認証をもう一度設定してください"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> が正常に機能せず、削除されました。指紋認証でスマートフォンのロックを解除するには、設定し直してください。"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> と <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> が正常に機能せず、削除されました。指紋認証でスマートフォンのロックを解除するには、設定し直してください。"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"顔認証をもう一度設定してください"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"顔モデルが正常に機能せず、削除されました。顔認証でスマートフォンのロックを解除するには、設定し直してください。"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"設定"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"後で"</string> </resources> diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml index 2beae0107f76..a28471995d96 100644 --- a/core/res/res/values-ka/strings.xml +++ b/core/res/res/values-ka/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"გასაგებია"</string> <string name="fcComplete" msgid="1080909484660507044">"ფუნქციის კოდი შესრულდა."</string> <string name="fcError" msgid="5325116502080221346">"კავშირის პრობლემაა ან არასწორი ფუნქციური კოდია."</string> <string name="httpErrorOk" msgid="6206751415788256357">"კარგი"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ხმოვანი ასისტ."</string> <string name="global_action_lockdown" msgid="2475471405907902963">"დაბლოკვა"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"ახალი შეტყობინება"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ფიზიკური კლავიატურა"</string> <string name="notification_channel_security" msgid="8516754650348238057">"უსაფრთხოება"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"ეკრანის ანაბეჭდის გადაღება"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"შეუძლია ეკრანის ანაბეჭდის გადაღება."</string> <string name="dream_preview_title" msgid="5570751491996100804">"გადახედვა, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"დახურვა"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"სტატუსის ზოლის გათიშვა ან ცვლილება"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"აპს შეეძლება სტატუსების ზოლის გათიშვა და სისტემის ხატულების დამატება/წაშლა."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"სტატუსის ზოლის ჩანაცვლება"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"სატელეფონო ზარების განხორციელება მხოლოდ თქვენი პირადი ტელეფონის აპიდან შეგიძლიათ. პირადი ტელეფონიდან განხორციელებული ზარები დაემატება პირადი საუბრის ისტორიაში."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS შეტყობინებების გაგზავნა მხოლოდ თქვენი პირადი შეტყობინებების აპიდან შეგიძლიათ."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"პირადი ბრაუზერის გამოყენება"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"სამსახურის ბრაუზერის გამოყენება"</string> <string name="miniresolver_call" msgid="6386870060423480765">"დარეკვა"</string> @@ -2407,13 +2396,17 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"დაყენდა კლავიატურის განლაგება: <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… შეეხეთ შესაცვლელად."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"ფიზიკური კლავიატურები კონფიგურირებულია"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"შეეხეთ კლავიატურების სანახავად"</string> - <string name="profile_label_private" msgid="6463418670715290696">"პირადი"</string> + <string name="profile_label_private" msgid="6463418670715290696">"კერძო"</string> <string name="profile_label_clone" msgid="769106052210954285">"კლონი"</string> <string name="profile_label_work" msgid="3495359133038584618">"სამსახური"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"სამსახური 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"სამსახური 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"სატესტო"</string> <string name="profile_label_communal" msgid="8743921499944800427">"საერთო"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"სამსახურის პროფილი"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"კერძო სივრცე"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"კლონის შექმნა"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"საერთო"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"სენსიტიური შეტყობინების კონტენტი დამალულია"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ეკრანის გაზიარებიდან აპის კონტენტი დამალულია უსაფრთხოების მიზნით"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ანაბეჭდით განბლოკვის ხელახლა დაყენება"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> კარგად არ მუშაობდა და წაიშალა. თავიდან დააყენეთ, რათა თქვენი ტელეფონი თითის ანაბეჭდის საშუალებით განბლოკოთ."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> და <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> კარგად არ მუშაობდნენ და წაიშალა. თავიდან დააყენეთ, რათა თქვენი ტელეფონი თითის ანაბეჭდის საშუალებით განბლოკოთ."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"დააყენეთ სახით განბლოკვა ხელახლა"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"თქვენი სახის მოდელი კარგად არ მუშაობდა და წაიშალა. თავიდან დააყენეთ, რათა თქვენი ტელეფონი სახის საშუალებით განბლოკოთ."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"დაყენება"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ახლა არა"</string> </resources> diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml index 6f02f525e1fd..e407c09b5a4e 100644 --- a/core/res/res/values-kk/strings.xml +++ b/core/res/res/values-kk/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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_NETWORK">%2$s</xliff:g> SIM картасын қолдану кезінде маңайдағы желіде сағат <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> SIM картасын қолдану кезінде маңайдағы желіде сағат <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> 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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобильдік желінің қауіпсіздік параметрлері"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Толық ақпарат"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Түсінікті"</string> <string name="fcComplete" msgid="1080909484660507044">"Функция коды толық."</string> <string name="fcError" msgid="5325116502080221346">"Байланыс мәселесі немесе функция коды жарамсыз."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Жарайды"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Дауыс көмекшісі"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Құлыптау"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Жаңа хабарландыру"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физикалық пернетақта"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Қауіпсіздік"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Скриншот жасау"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Дисплейдің скриншотын жасай аласыз."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Алғы көрініс, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"жабу"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"күйін көрсету тақтасын өшіру немесе өзгерту"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Қолданбаға күй жолағын өшіруге немесе жүйелік белгішелерді қосуға және жоюға рұқсат береді."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"күй жолағы болу"</string> @@ -1977,7 +1966,7 @@ <string name="maximize_button_text" msgid="4258922519914732645">"Жазу"</string> <string name="close_button_text" msgid="10603510034455258">"Жабу"</string> <string name="notification_messaging_title_template" msgid="772857526770251989">"<xliff:g id="CONVERSATION_TITLE">%1$s</xliff:g>: <xliff:g id="SENDER_NAME">%2$s</xliff:g>"</string> - <string name="call_notification_answer_action" msgid="5999246836247132937">"Жауап"</string> + <string name="call_notification_answer_action" msgid="5999246836247132937">"Жауап беру"</string> <string name="call_notification_answer_video_action" msgid="2086030940195382249">"Бейне"</string> <string name="call_notification_decline_action" msgid="3700345945214000726">"Қабылдамау"</string> <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Тұтқаны қою"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Жеке Телефон қолданбасынан ғана қоңырау шала аласыз. Бұл қолданба арқылы жасалған қоңыраулар жеке қоңыраулар тарихына қосылады."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS хабарларын жеке Messages қолданбасынан ғана жібере аласыз."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Жеке браузерді пайдалану"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Жұмыс браузерін пайдалану"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Қоңырау шалу"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Жұмыс 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Сынақ"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Жалпы"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Жұмыс профилі"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Құпия кеңістік"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Жалпы"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Хабарландырудың құпия контенті жасырылған."</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Қауіпсіздік мақсатында қолданба контенті экранды көрсету кезінде жасырылды."</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Жерсерік қызметіне автоматты түрде қосылды"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Саусақ ізімен ашу функциясын қайта реттеу"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> саусақ ізі дұрыс істемегендіктен жойылды. Телефонды саусақ ізімен ашу үшін оны қайта реттеңіз."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Саусақ іздері (<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> және <xliff:g id="FINGERPRINT_1">%2$s</xliff:g>) дұрыс істемегендіктен жойылды. Телефонды саусақ ізімен ашу үшін оларды қайта реттеңіз."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Бет тану функциясын қайта реттеу"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Бет үлгісі дұрыс істемегендіктен жойылды. Телефонды бетпен ашу үшін оны қайта реттеңіз."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Реттеу"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Қазір емес"</string> </resources> diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml index 55d736640c35..4fe436890a22 100644 --- a/core/res/res/values-km/strings.xml +++ b/core/res/res/values-km/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ការកំណត់សុវត្ថិភាពបណ្ដាញទូរសព្ទចល័ត"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ស្វែងយល់បន្ថែម"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"យល់ហើយ"</string> <string name="fcComplete" msgid="1080909484660507044">"កូដលក្ខណៈពេញលេញ។"</string> <string name="fcError" msgid="5325116502080221346">"បញ្ហាការតភ្ជាប់ ឬកូដលក្ខណៈមិនត្រឹមត្រូវ។"</string> <string name="httpErrorOk" msgid="6206751415788256357">"យល់ព្រម"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ជំនួយសម្លេង"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"ការចាក់សោ"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"ការជូនដំណឹងថ្មី"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ក្ដារចុចរូបវន្ត"</string> <string name="notification_channel_security" msgid="8516754650348238057">"សុវត្ថិភាព"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"អ្នកអាចធ្វើការហៅទូរសព្ទពីកម្មវិធីទូរសព្ទផ្ទាល់ខ្លួនរបស់អ្នកតែប៉ុណ្ណោះ។ ការហៅទូរសព្ទដែលបានធ្វើឡើងដោយប្រើទូរសព្ទផ្ទាល់ខ្លួននឹងត្រូវបានបញ្ចូលទៅប្រវត្តិហៅទូរសព្ទផ្ទាល់ខ្លួនរបស់អ្នក។"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"អ្នកអាចផ្ញើសារ SMS ពីកម្មវិធី Messages ផ្ទាល់ខ្លួនរបស់អ្នកតែប៉ុណ្ណោះ។"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ប្រើកម្មវិធីរុករកតាមអ៊ីនធឺណិតផ្ទាល់ខ្លួន"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"ប្រើកម្មវិធីរុករកតាមអ៊ីនធឺណិតសម្រាប់ការងារ"</string> <string name="miniresolver_call" msgid="6386870060423480765">"ហៅទូរសព្ទ"</string> @@ -2413,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"ការងារទី 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ការធ្វើតេស្ត"</string> <string name="profile_label_communal" msgid="8743921499944800427">"ទូទៅ"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"កម្រងព័ត៌មានការងារ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"លំហឯកជន"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ក្លូន"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ទូទៅ"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"បានលាក់ខ្លឹមសារជូនដំណឹងដែលមានលក្ខណៈរសើប"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"បានលាក់ខ្លឹមសារកម្មវិធីពីការបង្ហាញអេក្រង់ដើម្បីសុវត្ថិភាព"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"រៀបចំការដោះសោដោយស្កេនស្នាមម្រាមដៃម្ដងទៀត"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> មិនដំណើរការល្អទេ ហើយត្រូវបានលុបចេញហើយ។ រៀបចំវាម្ដងទៀត ដើម្បីដោះសោទូរសព្ទរបស់អ្នកដោយប្រើស្នាមម្រាមដៃ។"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> និង <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> មិនដំណើរការល្អទេ ហើយត្រូវបានលុបចេញហើយ។ រៀបចំវាម្ដងទៀត ដើម្បីដោះសោទូរសព្ទរបស់អ្នកដោយប្រើស្នាមម្រាមដៃ។"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"រៀបចំការដោះសោដោយស្កេនមុខម្ដងទៀត"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"គំរូមុខរបស់អ្នកមិនដំណើរការល្អទេ ហើយត្រូវបានលុបចេញហើយ។ រៀបចំវាម្ដងទៀត ដើម្បីដោះសោទូរសព្ទរបស់អ្នកដោយប្រើមុខ។"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"រៀបចំ"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"កុំទាន់"</string> </resources> diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml index 1ffcf1052268..3223c4089d68 100644 --- a/core/res/res/values-kn/strings.xml +++ b/core/res/res/values-kn/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> ಸೆಕೆಂಡುಗಳ ನಂತರ <xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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>ಕ್ಕೆ, <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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ಅರ್ಥವಾಯಿತು"</string> <string name="fcComplete" msgid="1080909484660507044">"ವೈಶಿಷ್ಟ್ಯದ ಕೋಡ್ ಪೂರ್ಣಗೊಂಡಿದೆ."</string> <string name="fcError" msgid="5325116502080221346">"ಸಂಪರ್ಕದ ಸಮಸ್ಯೆ ಅಥವಾ ಅಮಾನ್ಯ ವೈಶಿಷ್ಟ್ಯದ ಕೋಡ್."</string> <string name="httpErrorOk" msgid="6206751415788256357">"ಸರಿ"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ಧ್ವನಿ ಸಹಾಯಕ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"ಲಾಕ್ಡೌನ್"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"ಹೊಸ ನೋಟಿಫಿಕೇಶನ್"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ಭೌತಿಕ ಕೀಬೋರ್ಡ್"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ಭದ್ರತೆ"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <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> <string name="miniresolver_call" msgid="6386870060423480765">"ಕರೆ ಮಾಡಿ"</string> @@ -2413,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"ಕೆಲಸ 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ಪರೀಕ್ಷೆ"</string> <string name="profile_label_communal" msgid="8743921499944800427">"ಸಮುದಾಯ"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ಕ್ಲೋನ್"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ಸಮುದಾಯ"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"ಸೂಕ್ಷ್ಮ ನೋಟಿಫಿಕೇಶನ್ ಕಂಟೆಂಟ್ ಅನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ಭದ್ರತೆಗಾಗಿ ಸ್ಕ್ರೀನ್ ಹಂಚಿಕೊಳ್ಳುವಿಕೆಯಲ್ಲಿ ಆ್ಯಪ್ ಕಂಟೆಂಟ್ ಅನ್ನು ಮರೆಮಾಡಲಾಗಿದೆ"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ಫಿಂಗರ್ಪ್ರಿಂಟ್ ಅನ್ಲಾಕ್ ಅನ್ನು ಮತ್ತೊಮ್ಮೆ ಸೆಟಪ್ ಮಾಡಿ"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿಲ್ಲ ಹಾಗೂ ಅದನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಫಿಂಗರ್ ಪ್ರಿಂಟ್ ಮೂಲಕ ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಲು ಅದನ್ನು ಪುನಃ ಸೆಟಪ್ ಮಾಡಿ."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ಮತ್ತು <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿಲ್ಲ ಹಾಗೂ ಅವುಗಳನ್ನು ಅಳಿಸಲಾಗಿದೆ. ನಿಮ್ಮ ಫಿಂಗರ್ ಪ್ರಿಂಟ್ ಮೂಲಕ ನಿಮ್ಮ ಫೋನ್ ಅನ್ಲಾಕ್ ಮಾಡಲು ಅವುಗಳನ್ನು ಪುನಃ ಸೆಟಪ್ ಮಾಡಿ."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ಫೇಸ್ ಅನ್ಲಾಕ್ ಅನ್ನು ಮತ್ತೊಮ್ಮೆ ಸೆಟಪ್ ಮಾಡಿ"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"ನಿಮ್ಮ ಫೇಸ್ ಮಾಡೆಲ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸುತ್ತಿಲ್ಲ ಹಾಗೂ ಅದನ್ನು ಅಳಿಸಲಾಗಿದೆ. ಫೇಸ್ ಮೂಲಕ ನಿಮ್ಮ ಫೋನ್ ಅನ್ನು ಅನ್ಲಾಕ್ ಮಾಡಲು ಅದನ್ನು ಪುನಃ ಸೆಟಪ್ ಮಾಡಿ."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"ಸೆಟಪ್ ಮಾಡಿ"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ಈಗ ಬೇಡ"</string> </resources> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index a6cfe2a69926..7a8d324f0950 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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>에 <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즉, 사용자의 위치, 활동 또는 ID가 기록되었습니다. 이는 일반적인 관행이지만 개인 정보 보호에 대해 우려하는 사용자에게는 문제가 될 수도 있습니다."</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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"모바일 네트워크 보안 설정"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"자세히 알아보기"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"확인"</string> <string name="fcComplete" msgid="1080909484660507044">"기능 코드가 완료되었습니다."</string> <string name="fcError" msgid="5325116502080221346">"연결에 문제가 있거나 기능 코드가 잘못되었습니다."</string> <string name="httpErrorOk" msgid="6206751415788256357">"확인"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"음성 지원"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"잠금"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"새 알림"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"물리적 키보드"</string> <string name="notification_channel_security" msgid="8516754650348238057">"보안"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"스크린샷 촬영"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"디스플레이 스크린샷을 촬영할 수 있습니다."</string> <string name="dream_preview_title" msgid="5570751491996100804">"미리보기, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"닫기"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"상태 표시줄 사용 중지 또는 수정"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"앱이 상태 표시줄을 사용중지하거나 시스템 아이콘을 추가 및 제거할 수 있도록 허용합니다."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"상태 표시줄에 위치"</string> @@ -1401,7 +1390,7 @@ <string name="no_permissions" msgid="5729199278862516390">"권한 필요 없음"</string> <string name="perm_costs_money" msgid="749054595022779685">"비용이 부과될 수 있습니다."</string> <string name="dlg_ok" msgid="5103447663504839312">"확인"</string> - <string name="usb_charging_notification_title" msgid="1674124518282666955">"이 기기를 USB로 충전 중."</string> + <string name="usb_charging_notification_title" msgid="1674124518282666955">"이 기기를 USB로 충전 중"</string> <string name="usb_supplying_notification_title" msgid="5378546632408101811">"USB를 통해 연결된 기기 충전"</string> <string name="usb_mtp_notification_title" msgid="1065989144124499810">"USB 파일 전송 사용 설정됨"</string> <string name="usb_ptp_notification_title" msgid="5043437571863443281">"USB를 통해 PTP 사용 설정됨"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"개인 전화 앱에서만 전화를 걸 수 있습니다. 개인 전화로 통화한 내역은 개인 통화 기록에 추가됩니다."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"개인 메시지 앱에서만 SMS 메시지를 보낼 수 있습니다."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"개인 브라우저 사용"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"직장 브라우저 사용"</string> <string name="miniresolver_call" msgid="6386870060423480765">"통화"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"직장 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"테스트"</string> <string name="profile_label_communal" msgid="8743921499944800427">"공동"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"직장 프로필"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"비공개 스페이스"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"클론"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"공동"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"민감한 알림 콘텐츠 숨김"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"보안을 위해 화면 공유에서 앱 콘텐츠가 숨겨집니다."</string> <string name="satellite_notification_title" msgid="4026338973463121526">"위성에 자동 연결됨"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"지문 잠금 해제 다시 설정"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g>이(가) 제대로 작동하지 않아 삭제되었습니다. 지문으로 휴대전화를 잠금 해제하려면 다시 설정하세요."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> 및 <xliff:g id="FINGERPRINT_1">%2$s</xliff:g>이(가) 제대로 작동하지 않아 삭제되었습니다. 지문으로 휴대전화를 잠금 해제하려면 다시 설정하세요."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"얼굴 인식 잠금 해제 다시 설정"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"얼굴 모델이 제대로 작동하지 않아 삭제되었습니다. 얼굴로 휴대전화를 잠금 해제하려면 다시 설정하세요."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"설정"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"나중에"</string> </resources> diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml index e21a76c44eda..8a233a3108a4 100644 --- a/core/res/res/values-ky/strings.xml +++ b/core/res/res/values-ky/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобилдик тармактын коопсуздук параметрлери"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Кеңири маалымат"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Түшүндүм"</string> <string name="fcComplete" msgid="1080909484660507044">"Функция коду аткарылды."</string> <string name="fcError" msgid="5325116502080221346">"Туташууда көйгөй чыкты же функция коду жараксыз."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Жарайт"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Үн жардамчысы"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Бекем кулпулоо"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Жаңы эскертме"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Аппараттык баскычтоп"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Коопсуздук"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Скриншот тартып алуу"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Дисплейдин скриншотун тартып алсаңыз болот."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Алдын ала көрүү, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"жабуу"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"абал тилкесин өчүрүү же өзгөртүү"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Колдонмого абал тилкесин өчүрүү же тутум сүрөтчөлөрүн кошуу же алып салуу мүмкүнчүлүгүн берет."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"абал тилкесинин милдетин аткаруу"</string> @@ -1347,7 +1336,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> @@ -1409,7 +1398,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> @@ -1981,7 +1970,7 @@ <string name="call_notification_answer_video_action" msgid="2086030940195382249">"Видео"</string> <string name="call_notification_decline_action" msgid="3700345945214000726">"Четке кагуу"</string> <string name="call_notification_hang_up_action" msgid="9130720590159188131">"Чалууну бүтүрүү"</string> - <string name="call_notification_incoming_text" msgid="6143109825406638201">"Кирүүчү чалуу"</string> + <string name="call_notification_incoming_text" msgid="6143109825406638201">"Чалып жатат"</string> <string name="call_notification_ongoing_text" msgid="3880832933933020875">"Учурдагы чалуу"</string> <string name="call_notification_screening_text" msgid="8396931408268940208">"Кирүүчү чалууну иргөө"</string> <string name="default_notification_channel_label" msgid="3697928973567217330">"Категорияларга бөлүнгөн эмес"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Жеке \"Телефон\" колдонмосунан гана чала аласыз. Жеке \"Телефон\" колдонмосунан аткарылган чалуулар жеке чалуулар таржымалына кошулат."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS билдирүүлөрдү жеке Жазышуулар колдонмосунан гана жөнөтө аласыз."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Жеке серепчини колдонуу"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Жумуш серепчисин колдонуу"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Чалуу"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Жумуш 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Сыноо"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Жалпы"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Жумуш профили"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Жеке мейкиндик"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Жалпы"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Купуя билдирменин мазмуну жашырылган"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Коопсуздук үчүн колдонмодогу контент бөлүшүлгөн экрандан жашырылды"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Спутникке автоматтык түрдө туташтырылган"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Манжа изи менен ачуу функциясын кайра тууралаңыз"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ойдогудай иштебегендиктен, жок кылынды. Телефондо Манжа изи менен ачуу функциясын колдонуу үчүн аны кайра тууралаңыз."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> жана <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ойдогудай иштебегендиктен, жок кылынды. Телефонду манжа изи менен ачуу үчүн аларды кайра тууралаңыз."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Жүзүнөн таанып ачуу функциясын кайрадан тууралаңыз"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Жүзүңүздүн үлгүсү ойдогудай иштебегендиктен, жок кылынды. Телефондо Жүзүнөн таанып ачуу функциясын колдонуу үчүн аны кайра тууралаңыз."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Тууралоо"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Азыр эмес"</string> </resources> diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml index 4624d5ed5183..3e06b3da8ddc 100644 --- a/core/res/res/values-lo/strings.xml +++ b/core/res/res/values-lo/strings.xml @@ -283,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ຊ່ວຍເຫຼືອທາງສຽງ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"ລັອກໄວ້"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"ການແຈ້ງເຕືອນໃໝ່"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ແປ້ນພິມພາຍນອກ"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ຄວາມປອດໄພ"</string> @@ -2144,6 +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> + <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> @@ -2392,13 +2396,17 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"ຕັ້ງໂຄງຮ່າງແປ້ນພິມເປັນ <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… ແຕະເພື່ອປ່ຽນແປງ."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"ຕັ້ງຄ່າແປ້ນພິມແທ້ແລ້ວ"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"ແຕະເພື່ອເບິ່ງແປ້ນພິມ"</string> - <string name="profile_label_private" msgid="6463418670715290696">"ສ່ວນຕົວ"</string> + <string name="profile_label_private" msgid="6463418670715290696">"ສ່ວນບຸກຄົນ"</string> <string name="profile_label_clone" msgid="769106052210954285">"ໂຄລນ"</string> <string name="profile_label_work" msgid="3495359133038584618">"ວຽກ"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"ວຽກ 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"ວຽກ 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ທົດສອບ"</string> <string name="profile_label_communal" msgid="8743921499944800427">"ສ່ວນກາງ"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ພື້ນທີ່ສ່ວນບຸກຄົນ"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ໂຄລນ"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ສ່ວນກາງ"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"ເນື້ອຫາການແຈ້ງເຕືອນທີ່ລະອຽດອ່ອນເຊື່ອງຢູ່"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ເນື້ອຫາແອັບຖືກເຊື່ອງໄວ້ຈາກການແບ່ງປັນໜ້າຈໍເພື່ອຄວາມປອດໄພ"</string> @@ -2407,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ຕັ້ງຄ່າການປົດລັອກດ້ວຍລາຍນິ້ວມືຄືນໃໝ່"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ເຮັດວຽກໄດ້ບໍ່ດີ ແລະ ຖືກລຶບອອກແລ້ວ. ໃຫ້ຕັ້ງຄ່າມັນຄືນໃໝ່ເພື່ອປົດລັອກໂທລະສັບຂອງທ່ານດ້ວຍລາຍນິ້ວມື."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ແລະ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ເຮັດວຽກໄດ້ບໍ່ດີ ແລະ ຖືກລຶບອອກແລ້ວ. ໃຫ້ຕັ້ງຄ່າພວກມັນຄືນໃໝ່ເພື່ອປົດລັອກໂທລະສັບຂອງທ່ານດ້ວຍລາຍນິ້ວມື."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ຕັ້ງຄ່າການປົດລັອກດ້ວຍໜ້າຄືນໃໝ່"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"ຮູບແບບໃບໜ້າຂອງທ່ານເຮັດວຽກໄດ້ບໍ່ດີ ແລະ ຖືກລຶບອອກແລ້ວ. ໃຫ້ຕັ້ງຄ່າມັນຄືນໃໝ່ເພື່ອປົດລັອກໂທລະສັບຂອງທ່ານດ້ວຍໃບໜ້າ."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"ຕັ້ງຄ່າ"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ບໍ່ຟ້າວເທື່ອ"</string> </resources> diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml index 31ede3340871..50572f80e723 100644 --- a/core/res/res/values-lt/strings.xml +++ b/core/res/res/values-lt/strings.xml @@ -155,31 +155,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sek."</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neperadresuota"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neperadresuota"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobiliojo ryšio tinklo apsauga"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Šifruotė, nešifruotų tinklų pranešimai"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Pasiektas įrenginio ID"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> netoliese esantis tinklas įrašė jūsų įrenginio unikalų ID (IMSI arba IMEI), kol buvo naudojama jūsų „<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>“ SIM kortelė."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> netoliese esantis tinklas įrašė jūsų įrenginio unikalų ID (IMSI arba IMEI), kol buvo naudojama jūsų „<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>“ SIM kortelė.\n\nTai reiškia, kad jūsų vietovė, veikla ar tapatybė buvo užregistruoti. Tai įprasta praktika, bet žmonėms, kurie nerimauja dėl privatumo, gali kilti problemų."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Prisijungta prie šifruoto tinklo „<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 kortelės ryšys dabar saugesnis"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Prisijungta prie nešifruoto tinklo"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Skambučiai, pranešimai ir duomenys dabar gerokai pažeidžiamesni, kol naudojama „<xliff:g id="NETWORK_NAME">%1$s</xliff:g>“ SIM kortelė."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Skambučiai, pranešimai ir duomenys dabar gerokai pažeidžiamesni, kol naudojama „<xliff:g id="NETWORK_NAME">%1$s</xliff:g>“ SIM kortelė.\n\nKai ryšys vėl bus užšifruotas, gausite kitą pranešimą."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobiliojo ryšio tinklo saugos nustatymai"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Sužinokite daugiau"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Supratau"</string> <string name="fcComplete" msgid="1080909484660507044">"Funkcijos kodas baigtas."</string> <string name="fcError" msgid="5325116502080221346">"Ryšio problema arba neteisingas funkcijos kodas."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Gerai"</string> @@ -297,6 +285,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Užrakinimas"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Naujas pranešimas"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizinė klaviatūra"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sauga"</string> @@ -2158,6 +2148,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Veiksmų sekos režimo informacijos pranešimas"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Įjungta akumuliatoriaus tausojimo priemonė"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Sumažinamas akumuliatoriaus energijos vartojimas, kad akumuliatorius veiktų ilgiau"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Akumuliatoriaus tausojimo priemonė įjungta"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Akumuliatoriaus tausojimo priemonė įjungta, kad akumuliatorius veiktų ilgiau"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akumuliatoriaus tausojimo priemonė"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akumuliatoriaus tausojimo priemonė išjungta"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonas pakankamai įkrautas. Funkcijos neberibojamos."</string> @@ -2232,10 +2224,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Perjungti į darbo programą?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Jūsų organizacija leidžia skambinti tik iš darbo programų"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Jūsų organizacija leidžia siųsti pranešimus tik iš darbo programų"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Galite skambinti telefonu tik iš asmeninės Telefono programos. Naudojant asmeninę Telefono programą atlikti skambučiai bus pridėti prie jūsų asmeninės skambučių istorijos."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Galite siųsti SMS pranešimus tik iš asmeninės programos „Messages“."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Naudoti asmeninę naršyklę"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Naudoti darbo naršyklę"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Skambinti"</string> @@ -2415,6 +2405,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Darbas (3)"</string> <string name="profile_label_test" msgid="9168641926186071947">"Bandymas"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Bendruomenės"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Darbo profilis"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privati erdvė"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klonuoti"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Bendruomenės"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Neskelbtinos informacijos pranešimo turinys paslėptas"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Programos turinys paslėptas bendrinant ekraną saugumo sumetimais"</string> @@ -2423,4 +2417,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Atrakinimo piršto atspaudu nustatymas dar kartą"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> neveikė tinkamai ir buvo ištrintas. Nustatykite jį dar kartą, kad atrakintumėte telefoną piršto atspaudu."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ir <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> neveikė tinkamai ir buvo ištrinti. Nustatykite juos dar kartą, kad atrakintumėte telefoną piršto atspaudu."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Atrakinimo pagal veidą nustatymas iš naujo"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Jūsų veido modelis neveikė tinkamai ir buvo ištrintas. Nustatykite jį dar kartą, kad atrakintumėte telefoną pagal veidą."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Nustatyti"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ne dabar"</string> </resources> diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml index 93d6a4f0aba1..8be327da1aeb 100644 --- a/core/res/res/values-lv/strings.xml +++ b/core/res/res/values-lv/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pēc <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundes(-ēm)"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nav pāradresēts"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nav pāradresēts"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobilā tīkla drošība"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Šifrēšana un paziņojumi par nešifrētiem tīkliem"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Piekļūts ierīces ID"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Pulksten <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> tuvējā tīklā tika reģistrēts jūsu ierīces unikālais ID (IMSI vai IMEI), kamēr izmantojāt savu <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM karti."</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Pulksten <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> tuvējā tīklā tika reģistrēts jūsu ierīces unikālais ID (IMSI vai IMEI), kamēr izmantojāt savu <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM karti.\n\nTas nozīmē, ka tika reģistrēta jūsu atrašanās vieta, darbības vai identitāte. Šāda prakse ir ierasta, tomēr var radīt problēmas personām, kurām ir bažas par konfidencialitāti."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Izveidots savienojums ar šifrēto tīklu <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Tagad <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM savienojums ir drošāks."</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Izveidots savienojums ar nešifrētu tīklu"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Pašlaik, kamēr izmantojat <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM karti, zvani, ziņojumi un dati ir mazāk aizsargāti."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Pašlaik, kamēr izmantojat <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM karti, zvani, ziņojumi un dati ir mazāk aizsargāti.\n\nKad savienojums atkal būs šifrēts, saņemsiet jaunu paziņojumu."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobilā tīkla drošības iestatījumi"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Uzzināt vairāk"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Labi"</string> <string name="fcComplete" msgid="1080909484660507044">"Funkcijas kods ir pabeigts."</string> <string name="fcError" msgid="5325116502080221346">"Savienojuma problēma vai nederīgs funkcijas kods."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Labi"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Balss palīgs"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloķēšana"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"Pārsniedz"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Jauns paziņojums"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziskā tastatūra"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Drošība"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ekrānuzņēmuma izveide"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Var izveidot displeja ekrānuzņēmumu."</string> <string name="dream_preview_title" msgid="5570751491996100804">"<xliff:g id="DREAM_NAME">%1$s</xliff:g> (priekšskatījums)"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"nerādīt"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"atspējot vai pārveidot statusa joslu"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Ļauj lietotnei atspējot statusa joslu vai pievienot un noņemt sistēmas ikonas."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"Būt par statusa joslu"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Informatīvs paziņojums par akumulatoru"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Ieslēgts akumulatora enerģijas taupīšanas režīms"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Tiek samazināts akumulatora lietojums, lai paildzinātu akumulatora darbību."</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Akumulatora enerģijas taupīšanas režīms ir ieslēgts"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Akumulatora enerģijas taupīšanas režīms ir ieslēgts, lai paildzinātu akumulatora darbību."</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Akumulatora enerģijas taupīšanas režīms"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Akumulatora enerģijas taupīšanas režīms ir izslēgts"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Tālruņa uzlādes līmenis ir pietiekams. Funkcijas vairs netiek ierobežotas."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Vai pārslēgties uz darba lietotni?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Jūsu organizācija ļauj jums veikt zvanus tikai no darba lietotnēm."</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Jūsu organizācija ļauj jums sūtīt ziņojumus tikai no darba lietotnēm."</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Tālruņa zvanus varat veikt tikai no personīgās lietotnes Tālrunis. Zvani, kam izmantosiet personīgo lietotni Tālrunis, tiks pievienoti jūsu personīgajai zvanu vēsturei."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Īsziņas varat sūtīt tikai no personīgās lietotnes Ziņojumi."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Izmantot personīgo pārlūku"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Izmantot darba pārlūku"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Zvanīt"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Darbam (3.)"</string> <string name="profile_label_test" msgid="9168641926186071947">"Testēšanai"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Kopīgs"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Darba profils"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privātā telpa"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klons"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Kopīgs"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitīvs paziņojuma saturs ir paslēpts"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Drošības nolūkos lietotnes saturs kopīgotajā ekrānā ir paslēpts"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automātiski izveidots savienojums ar satelītu"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Vēlreiz iestatiet autorizāciju ar pirksta nospiedumu"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nedarbojās pareizi un tika izdzēsts. Iestatiet to atkal, lai varētu atbloķēt tālruni ar pirksta nospiedumu."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> un <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nedarbojās pareizi un tika izdzēsti. Iestatiet tos atkal, lai varētu atbloķētu tālruni ar pirksta nospiedumu."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Vēlreiz iestatiet autorizāciju pēc sejas"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Jūsu sejas modelis nedarbojās pareizi un tika izdzēsts. Iestatiet to atkal, lai varētu atbloķēt tālruni ar seju."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Iestatīt"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ne tagad"</string> </resources> diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml index 0166b6721058..680ff0a4b1b1 100644 --- a/core/res/res/values-mk/strings.xml +++ b/core/res/res/values-mk/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Во <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, мрежа во близина го сними уникатниот ID (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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Сфатив"</string> <string name="fcComplete" msgid="1080909484660507044">"Кодот за карактеристиката заврши."</string> <string name="fcError" msgid="5325116502080221346">"Проблем со поврзувањето или неважечки код за карактеристиката."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Во ред"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Гласовна помош"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Заклучување"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Ново известување"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физичка тастатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Безбедност"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Зачувување слика од екранот"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може да зачува слика од екранот."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Преглед, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"отфрли"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"оневозможи или измени статусна лента"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Дозволува апликацијата да ја оневозможи статусната лента или да додава или отстранува системски икони."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"да стане статусна лента"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Може да упатувате телефонски повици само од вашата лична апликација „Телефон“. Повиците упатени со личен телефон ќе се додадат во вашата лична историја на повици."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Може да испраќате SMS-пораки само од вашата лична апликација Messages."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Користи личен прелистувач"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Користи работен прелистувач"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Повикај"</string> @@ -2407,15 +2396,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Распоредот на тастатурата е поставен на <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Допрете за да промените."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Физичките тастатури се конфигурирани"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Допрете за да ги видите тастатурите"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Приватен профил"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Приватно"</string> <string name="profile_label_clone" msgid="769106052210954285">"Клониран профил"</string> - <string name="profile_label_work" msgid="3495359133038584618">"Работен профил"</string> + <string name="profile_label_work" msgid="3495359133038584618">"Работно"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Работен профил 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Работен профил 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Профил за тестирање"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Профил на заедницата"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Работен профил"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Приватен простор"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клониран профил"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Профил на заедницата"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Содржината на чувствителните известувања е скриена"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Од безбедносни причини, содржините на апликацијата се скриени од споделувањето екран"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Поврзано со сателит автоматски"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Поставете „Отклучување со отпечаток“ повторно"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> не функционираше добро, па се избриша. Поставете го повторно за да го отклучувате телефонот со отпечаток."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> и <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> не функционираа добро, па се избришаа. Поставете ги повторно за да го отклучувате телефонот со отпечаток."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Поставете „Отклучување со лик“ повторно"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Вашиот модел на лик не функционираше добро, па се избриша. Поставете го повторно за да го отклучите телефонот со лик."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Поставете"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Не сега"</string> </resources> diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml index f90eddca408a..eca7f28651e8 100644 --- a/core/res/res/values-ml/strings.xml +++ b/core/res/res/values-ml/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> നിമിഷത്തിനുശേഷം <xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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_NETWORK">%2$s</xliff:g> സിം ഉപയോഗിക്കുമ്പോൾ സമീപത്തുള്ള ഒരു നെറ്റ്വർക്ക് നിങ്ങളുടെ ഉപകരണത്തിൻ്റെ തനത് ഐഡി (IMSI അല്ലെങ്കിൽ IMEI), <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>-ന് റെക്കോർഡ് ചെയ്തു"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"നിങ്ങളുടെ <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> സിം ഉപയോഗിക്കുമ്പോൾ സമീപത്തുള്ള ഒരു നെറ്റ്വർക്ക് നിങ്ങളുടെ ഉപകരണത്തിൻ്റെ തനത് ഐഡി (IMSI അല്ലെങ്കിൽ IMEI), <xliff:g id="DISCLOSURE_TIME">%1$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> + <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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"മനസ്സിലായി"</string> <string name="fcComplete" msgid="1080909484660507044">"ഫീച്ചർ കോഡ് പൂർണ്ണമാണ്."</string> <string name="fcError" msgid="5325116502080221346">"കണക്ഷൻ പ്രശ്നം അല്ലെങ്കിൽ ഫീച്ചർ കോഡ് അസാധുവാണ്."</string> <string name="httpErrorOk" msgid="6206751415788256357">"ശരി"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"വോയ്സ് സഹായം"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"ലോക്ക്ഡൗൺ"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"പുതിയ അറിയിപ്പ്"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ഫിസിക്കൽ കീബോഡ്"</string> <string name="notification_channel_security" msgid="8516754650348238057">"സുരക്ഷ"</string> @@ -1916,8 +1906,8 @@ <string name="package_updated_device_owner" msgid="7560272363805506941">"നിങ്ങളുടെ അഡ്മിൻ അപ്ഡേറ്റ് ചെയ്യുന്നത്"</string> <string name="package_deleted_device_owner" msgid="2292335928930293023">"നിങ്ങളുടെ അഡ്മിൻ ഇല്ലാതാക്കുന്നത്"</string> <string name="confirm_battery_saver" msgid="5247976246208245754">"ശരി"</string> - <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"\'ബാറ്ററി ലാഭിക്കൽ\' ഡാർക്ക് തീം ഓണാക്കുന്നു, ഒപ്പം പശ്ചാത്തല ആക്റ്റിവിറ്റിയും ചില വിഷ്വൽ ഇഫക്റ്റുകളും ചില ഫീച്ചറുകളും ചില നെറ്റ്വർക്ക് കണക്ഷനുകളും പരിമിതപ്പെടുത്തുകയോ ഓഫാക്കുകയോ ചെയ്യുന്നു."</string> - <string name="battery_saver_description" msgid="8518809702138617167">"ബാറ്ററി ലാഭിക്കൽ ഡാർക്ക് തീം ഓണാക്കുന്നു, പശ്ചാത്തല ആക്റ്റിവിറ്റിയും ചില വിഷ്വൽ ഇഫക്റ്റുകളും ചില ഫീച്ചറുകളും ചില നെറ്റ്വർക്ക് കണക്ഷനുകളും അത് പരിമിതപ്പെടുത്തുകയോ ഓഫാക്കുകയോ ചെയ്യുന്നു."</string> + <string name="battery_saver_description_with_learn_more" msgid="5444908404021316250">"\'ബാറ്ററി സേവർ\' ഡാർക്ക് തീം ഓണാക്കുന്നു, ഒപ്പം പശ്ചാത്തല ആക്റ്റിവിറ്റിയും ചില വിഷ്വൽ ഇഫക്റ്റുകളും ചില ഫീച്ചറുകളും ചില നെറ്റ്വർക്ക് കണക്ഷനുകളും പരിമിതപ്പെടുത്തുകയോ ഓഫാക്കുകയോ ചെയ്യുന്നു."</string> + <string name="battery_saver_description" msgid="8518809702138617167">"ബാറ്ററി സേവർ ഡാർക്ക് തീം ഓണാക്കുന്നു, പശ്ചാത്തല ആക്റ്റിവിറ്റിയും ചില വിഷ്വൽ ഇഫക്റ്റുകളും ചില ഫീച്ചറുകളും ചില നെറ്റ്വർക്ക് കണക്ഷനുകളും അത് പരിമിതപ്പെടുത്തുകയോ ഓഫാക്കുകയോ ചെയ്യുന്നു."</string> <string name="data_saver_description" msgid="4995164271550590517">"ഡാറ്റാ ഉപയോഗം കുറയ്ക്കാൻ സഹായിക്കുന്നതിനായി പശ്ചാത്തലത്തിൽ ഡാറ്റ അയയ്ക്കുകയോ സ്വീകരിക്കുകയോ ചെയ്യുന്നതിൽ നിന്ന് ചില ആപ്പുകളെ ഡാറ്റാ സേവർ തടയുന്നു. നിങ്ങൾ നിലവിൽ ഉപയോഗിക്കുന്ന ഒരു ആപ്പിന് ഡാറ്റ ആക്സസ് ചെയ്യാനാകും, എന്നാൽ വല്ലപ്പോഴും മാത്രമെ സംഭവിക്കുന്നുള്ളു. ഇതിനർത്ഥം, ഉദാഹരണമായി നിങ്ങൾ ടാപ്പ് ചെയ്യുന്നത് വരെ ചിത്രങ്ങൾ പ്രദർശിപ്പിക്കുകയില്ല എന്നാണ്."</string> <string name="data_saver_enable_title" msgid="7080620065745260137">"ഡാറ്റാ സേവർ ഓണാക്കണോ?"</string> <string name="data_saver_enable_button" msgid="4399405762586419726">"ഓണാക്കുക"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"നിങ്ങളുടെ സ്വകാര്യ ഫോൺ ആപ്പിൽ നിന്ന് മാത്രമേ ഫോൺ വിളിക്കാനാകൂ. സ്വകാര്യ ഫോൺ ഉപയോഗിച്ചുള്ള കോളുകൾ നിങ്ങളുടെ സ്വകാര്യ കോൾ ചരിത്രത്തിലേക്ക് ചേർക്കും."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"നിങ്ങളുടെ സ്വകാര്യ സന്ദേശമയയ്ക്കൽ ആപ്പിൽ നിന്ന് മാത്രമേ നിങ്ങൾക്ക് SMS സന്ദേശങ്ങൾ അയയ്ക്കാനാകൂ."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"വ്യക്തിപരമായ ബ്രൗസർ ഉപയോഗിക്കുക"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"ഔദ്യോഗിക ബ്രൗസർ ഉപയോഗിക്കുക"</string> <string name="miniresolver_call" msgid="6386870060423480765">"കോൾ ചെയ്യുക"</string> @@ -2396,7 +2386,7 @@ <string name="concurrent_display_notification_thermal_title" msgid="5921609404644739229">"ഉപകരണത്തിന് ചൂട് കൂടുതലാണ്"</string> <string name="concurrent_display_notification_thermal_content" msgid="2075484836527609319">"നിങ്ങളുടെ ഫോൺ വളരെയധികം ചൂടാകുന്നതിനാൽ ഡ്യുവൽ സ്ക്രീൻ ലഭ്യമല്ല"</string> <string name="concurrent_display_notification_power_save_title" msgid="1794569070730736281">"ഡ്യുവൽ സ്ക്രീൻ ലഭ്യമല്ല"</string> - <string name="concurrent_display_notification_power_save_content" msgid="2198116070583851493">"ബാറ്ററി ലാഭിക്കൽ ഓണായതിനാൽ ഡ്യുവൽ സ്ക്രീൻ ലഭ്യമല്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ ഓഫാക്കാം."</string> + <string name="concurrent_display_notification_power_save_content" msgid="2198116070583851493">"ബാറ്ററി സേവർ ഓണായതിനാൽ ഡ്യുവൽ സ്ക്രീൻ ലഭ്യമല്ല. നിങ്ങൾക്ക് ഇത് ക്രമീകരണത്തിൽ ഓഫാക്കാം."</string> <string name="device_state_notification_settings_button" msgid="691937505741872749">"ക്രമീകരണത്തിലേക്ക് പോകുക"</string> <string name="device_state_notification_turn_off_button" msgid="6327161707661689232">"ഓഫാക്കുക"</string> <string name="keyboard_layout_notification_selected_title" msgid="1202560174252421219">"<xliff:g id="DEVICE_NAME">%s</xliff:g> കോൺഫിഗർ ചെയ്തു"</string> @@ -2413,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"ഔദ്യോഗികം 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ടെസ്റ്റ്"</string> <string name="profile_label_communal" msgid="8743921499944800427">"കമ്മ്യൂണൽ"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"സ്വകാര്യ സ്പേസ്"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ക്ലോൺ ചെയ്യുക"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"കമ്മ്യൂണൽ"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"സൂക്ഷ്മമായി കൈകാര്യം ചെയ്യേണ്ട അറിയിപ്പ് ഉള്ളടക്കം മറച്ചിരിക്കുന്നു"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ആപ്പ് ഉള്ളടക്കം, അതിന്റെ സുരക്ഷയ്ക്കായി സ്ക്രീൻ പങ്കിടലിൽ നിന്ന് മറച്ചിരിക്കുന്നു"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ഫിംഗർപ്രിന്റ് അൺലോക്ക് വീണ്ടും സജ്ജീകരിക്കുക"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ശരിയായി പ്രവർത്തിക്കാത്തതിനാൽ അത് ഇല്ലാതാക്കി. നിങ്ങളുടെ ഫിംഗർപ്രിന്റ് ഉപയോഗിച്ച് ഫോൺ അൺലോക്ക് ചെയ്യുന്നതിനായി വീണ്ടും സജ്ജീകരിക്കുക."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>, <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> എന്നിവ ശരിയായി പ്രവർത്തിക്കാത്തതിനാൽ അവ ഇല്ലാതാക്കി. നിങ്ങളുടെ ഫിംഗർപ്രിന്റ് ഉപയോഗിച്ച് ഫോൺ അൺലോക്ക് ചെയ്യുന്നതിനായി അവ വീണ്ടും സജ്ജീകരിക്കുക."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ഫെയ്സ് അൺലോക്ക് വീണ്ടും സജ്ജീകരിക്കുക"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"നിങ്ങളുടെ മുഖ മോഡൽ ശരിയായി പ്രവർത്തിക്കാത്തതിനാൽ അത് ഇല്ലാതാക്കി. നിങ്ങളുടെ മുഖം ഉപയോഗിച്ച് ഫോൺ അൺലോക്ക് ചെയ്യുന്നതിനായി വീണ്ടും സജ്ജീകരിക്കുക."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"സജ്ജീകരിക്കുക"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ഇപ്പോൾ വേണ്ട"</string> </resources> diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml index 45eacc0850c2..8d430eedc396 100644 --- a/core/res/res/values-mn/strings.xml +++ b/core/res/res/values-mn/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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>-д таныг <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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Мобайл сүлжээний аюулгүй байдлын тохиргоо"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Нэмэлт мэдээлэл авах"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Ойлголоо"</string> <string name="fcComplete" msgid="1080909484660507044">"Онцлог код дуусав."</string> <string name="fcError" msgid="5325116502080221346">"Холболтын асуудал эсвэл буруу функцын код."</string> <string name="httpErrorOk" msgid="6206751415788256357">"ОК"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Дуут туслах"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Түгжих"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Шинэ мэдэгдэл"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Биет гар"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Аюулгүй байдал"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Дэлгэцийн зургийг дарах"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Дэлгэцийн зургийг дарах боломжтой."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Урьдчилан үзэх, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"хаах"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"статус самбарыг идэвхгүй болгох болон өөрчлөх"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Апп нь статус самбарыг идэвхгүй болгох эсвэл систем дүрсийг нэмэх, хасах боломжтой."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"статусын хэсэг болох"</string> @@ -1775,7 +1764,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> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Та зөвхөн хувийн гар утасны аппаасаа утасны дуудлага хийх боломжтой. Хувийн гар утасны аппаас хийсэн дуудлагыг таны хувийн дуудлагын түүхэд нэмнэ."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Та зөвхөн хувийн мессеж аппаасаа SMS мессеж илгээх боломжтой."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Хувийн хөтөч ашиглах"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Ажлын хөтөч ашиглах"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Залгах"</string> @@ -2407,15 +2396,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Гарын бүдүүвчийг <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g> болгож тохируулсан… Өөрчлөхийн тулд товшино уу."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Биет гарыг тохируулсан"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Гарыг харахын тулд товшино уу"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Хувийн"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Хаалттай"</string> <string name="profile_label_clone" msgid="769106052210954285">"Клон"</string> <string name="profile_label_work" msgid="3495359133038584618">"Ажил"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Ажил 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Ажил 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Туршилт"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Нийтийн"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Ажлын профайл"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Хаалттай орон зай"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клон"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Нийтийн"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Эмзэг мэдэгдлийн контентыг нуусан"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Аюулгүй байдлын улмаас аппын контентыг дэлгэц хуваалцахаас нуусан"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Хиймэл дагуулд автоматаар холбогдсон"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Хурууны хээгээр түгжээ тайлахыг дахин тохируулна уу"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> сайн ажиллахгүй байсан тул үүнийг устгасан. Утасныхаа түгжээг хурууны хээгээр тайлахын тулд хурууны хээг дахин тохируулна уу."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>, <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> сайн ажиллахгүй байсан тул эдгээрийг устгасан. Утасныхаа түгжээг хурууныхаа хээгээр тайлахын тулд хоёр хурууны хээг дахин тохируулна уу."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Царайгаар түгжээ тайлахыг дахин тохируулна уу"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Таны нүүрний загвар сайн ажиллахгүй байсан бөгөөд үүнийг устгасан. Утасныхаа түгжээг царайгаар тайлахын тулд нүүрний загварыг дахин тохируулна уу."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Тохируулах"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Одоо биш"</string> </resources> diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml index c2a9c731b6c8..d5f2bae87f0d 100644 --- a/core/res/res/values-mr/strings.xml +++ b/core/res/res/values-mr/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> सेकंदांनंतर <xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> सिम वापरत असताना तुमच्या डिव्हाइसचा युनिक आयडी (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> सिम वापरत असताना तुमच्या डिव्हाइसचा युनिक आयडी (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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"मोबाइल नेटवर्क सुरक्षेसंबंधित सेटिंग्ज"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"अधिक जाणून घ्या"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"समजले"</string> <string name="fcComplete" msgid="1080909484660507044">"वैशिष्ट्य कोड पूर्ण."</string> <string name="fcError" msgid="5325116502080221346">"कनेक्शन समस्या किंवा अवैध फीचर कोड."</string> <string name="httpErrorOk" msgid="6206751415788256357">"ठीक"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"व्हॉइस सहाय्य"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"लॉकडाउन"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"नवीन सूचना"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"वास्तविक कीबोर्ड"</string> <string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"work app वर स्विच करायचे आहे का?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"तुमची संस्था तुम्हाला फक्त work app वरून कॉल करण्याची अनुमती देते"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"तुमची संस्था तुम्हाला फक्त work app वरून मेसेज पाठवण्याची अनुमती देते"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"तुम्ही तुमच्या वैयक्तिक फोन ॲपवरून फक्त फोन कॉल करू शकता. वैयक्तिक फोन वापरून केलेले कॉल हे तुमच्या वैयक्तिक कॉल इतिहासामध्ये जोडले जातील."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"तुम्ही तुमच्या वैयक्तिक Messages ॲप वरून फक्त एसएमएस मेसेज पाठवू शकता."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"वैयक्तिक ब्राउझर वापरा"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"कार्य ब्राउझर वापरा"</string> <string name="miniresolver_call" msgid="6386870060423480765">"कॉल करा"</string> @@ -2413,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"ऑफिस ३"</string> <string name="profile_label_test" msgid="9168641926186071947">"चाचणी"</string> <string name="profile_label_communal" msgid="8743921499944800427">"सामुदायिक"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"कार्य प्रोफाइल"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"खाजगी स्पेस"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"क्लोन"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"सामुदायिक"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"संवेदनशील नोटिफिकेशनचा आशय लपवलेला आहे"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"स्क्रीन शेअर करताना सुरक्षेसाठी अॅपमधील आशय लपवला आहे"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"फिंगरप्रिंट अनलॉक पुन्हा सेट करा"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"योग्यरीत्या काम करत नसल्यामुळे <xliff:g id="FINGERPRINT">%s</xliff:g> हटवले गेले आहे. तुमचे फिंगरप्रिंट वापरून फोन अनलॉक करण्यासाठी ते पुन्हा सेट करा."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"परफॉर्मन्समध्ये सुधारणा करण्यासाठी आणि योग्यरीत्या काम करत नसल्यामुळे <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> व <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> हटवली गेली आहेत. तुमचे फिंगरप्रिंट वापरून फोन अनलॉक करण्यासाठी ते पुन्हा सेट करा."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"फेस अनलॉक पुन्हा सेट करा"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"तुमचे फेस मॉडेल योग्यरीत्या काम करत नसल्यामुळे ते हटवले गेले आहे. तुमचा चेहरा वापरून फोन अनलॉक करण्यासाठी ते पुन्हा सेट करा."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"सेट करा"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"आताच नको"</string> </resources> diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml index 5e9ce271cf5f..8018cb36741f 100644 --- a/core/res/res/values-ms/strings.xml +++ b/core/res/res/values-ms/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> selepas <xliff:g id="TIME_DELAY">{2}</xliff:g> saat"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak dimajukan"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Tidak dimajukan"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Keselamatan rangkaian mudah alih"</string> + <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 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> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Panggilan, mesej dan data lebih terdedah pada masa ini semasa menggunakan SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> anda"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Panggilan, mesej dan data lebih terdedah pada masa ini semasa menggunakan SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> anda.\n\nApabila sambungan anda disulitkan semula, anda akan menerima pemberitahuan lain."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Tetapan keselamatan rangkaian mudah alih"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Ketahui lebih lanjut"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Kod ciri selesai."</string> <string name="fcError" msgid="5325116502080221346">"Masalah sambungan atau kod ciri tidak sah."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Bantuan Suara"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Kunci semua"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Pemberitahuan baharu"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Papan kekunci fizikal"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Keselamatan"</string> @@ -2156,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Pemberitahuan maklumat Mod Rutin"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Penjimat Bateri dihidupkan"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Mengurangkan penggunaan bateri untuk melanjutkan hayat bateri"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Penjimat Bateri dihidupkan"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Penjimat Bateri dihidupkan untuk melanjutkan hayat bateri"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Penjimat Bateri"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Penjimat Bateri dimatikan"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Cas telefon mencukupi. Ciri tidak lagi dihadkan."</string> @@ -2230,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Beralih kepada apl kerja?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Organisasi anda hanya membenarkan anda membuat panggilan daripada apl kerja"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organisasi anda hanya membenarkan anda menghantar mesej daripada apl kerja"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Anda hanya boleh membuat panggilan telefon daripada apl Telefon peribadi anda. Panggilan yang dibuat dengan apl Telefon peribadi akan ditambahkan pada sejarah panggilan peribadi anda."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Anda hanya boleh menghantar mesej SMS daripada apl Messages peribadi anda."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Gunakan penyemak imbas peribadi"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Gunakan penyemak imbas kerja"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Panggil"</string> @@ -2406,13 +2396,17 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Reka letak papan kekunci ditetapkan kepada <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Ketik untuk menukar reka letak."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Papan kekunci fizikal dikonfigurasikan"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Ketik untuk melihat papan kekunci"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Peribadi"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Persendirian"</string> <string name="profile_label_clone" msgid="769106052210954285">"Klon"</string> <string name="profile_label_work" msgid="3495359133038584618">"Kerja"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Kerja 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Kerja 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Ujian"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Umum"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil kerja"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Ruang privasi"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Umum"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Kandungan pemberitahuan yang sensitif disembunyikan"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Kandungan apl disembunyikan daripada perkongsian skrin untuk keselamatan"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Sediakan Buka Kunci Cap Jari sekali lagi"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> tidak berfungsi dengan baik dan telah dipadamkan. Sediakan cap jari sekali lagi untuk membuka kunci telefon anda menggunakan cap jari."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> dan <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> tidak berfungsi dengan baik dan telah dipadamkan. Sediakan kedua-dua cap jari tersebut sekali lagi untuk membuka kunci telefon anda menggunakan cap jari anda."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Sediakan semula Buka Kunci Wajah"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Model wajah anda tidak berfungsi dengan baik dan telah dipadamkan. Sediakan model wajah sekali lagi untuk membuka kunci telefon anda menggunakan wajah."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Sediakan"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Bukan sekarang"</string> </resources> diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml index 81a79cf1e556..488bdb082f08 100644 --- a/core/res/res/values-my/strings.xml +++ b/core/res/res/values-my/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> တွင် သင့် <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="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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"မိုဘိုင်းကွန်ရက် လုံခြုံရေး ဆက်တင်များ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ပိုမိုလေ့လာရန်"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"နားလည်ပြီ"</string> <string name="fcComplete" msgid="1080909484660507044">"ပုံစံကုတ်ပြီးဆုံးသည်"</string> <string name="fcError" msgid="5325116502080221346">"ဆက်သွယ်မှုဆိုင်ရာပြသနာ သို့မဟုတ် တရားမဝင်သောပုံစံကုတ်"</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"အသံ အကူအညီ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"လော့ခ်ဒေါင်း"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"၉၉၉+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"အကြောင်းကြားချက်အသစ်"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"စက်၏ ကီးဘုတ်"</string> <string name="notification_channel_security" msgid="8516754650348238057">"လုံခြုံရေး"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"ဖန်သားပြင်ဓာတ်ပုံ ရိုက်ရန်"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"ဖန်သားပြင်ပြသမှုကို ဓာတ်ပုံရိုက်နိုင်ပါသည်။"</string> <string name="dream_preview_title" msgid="5570751491996100804">"<xliff:g id="DREAM_NAME">%1$s</xliff:g> အစမ်းကြည့်ခြင်း"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"ပယ်ရန်"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"အခြေအနေပြဘားအား အလုပ်မလုပ်ခိုင်းရန်သို့မဟုတ် မွမ်းမံရန်"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"အက်ပ်အား အခြေအနေပြ ဘားကို ပိတ်ခွင့် သို့မဟတ် စနစ် အိုင်ကွန်များကို ထည့်ခြင်း ဖယ်ရှားခြင်း ပြုလုပ်ခွင့် ပြုသည်။"</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"အခြေအနေပြ ဘားဖြစ်ပါစေ"</string> @@ -657,7 +646,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> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <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> <string name="miniresolver_call" msgid="6386870060423480765">"ဖုန်းဆက်ရန်"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"အလုပ် ၃"</string> <string name="profile_label_test" msgid="9168641926186071947">"စမ်းသပ်မှု"</string> <string name="profile_label_communal" msgid="8743921499944800427">"အများသုံး"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"အလုပ်ပရိုဖိုင်"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"သီးသန့်နေရာ"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ပုံတူပွားရန်"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"အများသုံး"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"သတိထားရမည့် အကြောင်းကြားချက်ပါ အချက်အလက်ကို ဖျောက်ထားသည်"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"အက်ပ်အကြောင်းအရာသည် လုံခြုံရေးအတွက် မျက်နှာပြင် မျှဝေခြင်းမှ ဖျောက်ထားသည်"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ဂြိုဟ်တုနှင့် အလိုအလျောက် ချိတ်ဆက်ထားသည်"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"‘လက်ဗွေသုံး လော့ခ်ဖွင့်ခြင်း’ ကို စနစ်ထပ်မံထည့်သွင်းပါ"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> သိပ်အဆင်မပြေသဖြင့် ဖျက်ထားသည်။ သင့်ဖုန်းကို လက်ဗွေဖြင့်လော့ခ်ဖွင့်ရန် ၎င်းကို စနစ်ထပ်မံထည့်သွင်းပါ။"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> နှင့် <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> တို့ သိပ်အဆင်မပြေသဖြင့် ဖျက်ထားသည်။ သင့်ဖုန်းကို လက်ဗွေဖြင့်လော့ခ်ဖွင့်ရန် ၎င်းတို့ကို စနစ်ထပ်မံထည့်သွင်းပါ။"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"‘မျက်နှာပြ လော့ခ်ဖွင့်ခြင်း’ ကို စနစ်ထပ်မံထည့်သွင်းပါ"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"သင့်မျက်နှာနမူနာ သိပ်အဆင်မပြေသဖြင့် ဖျက်ထားသည်။ သင့်ဖုန်းကို မျက်နှာဖြင့်လော့ခ်ဖွင့်ရန် ၎င်းကို စနစ်ထပ်မံထည့်သွင်းပါ။"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"စနစ်ထည့်သွင်းရန်"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ယခုမလုပ်ပါ"</string> </resources> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index efaae18bd5f8..3939f48b9265 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> etter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Ikke viderekoblet"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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 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> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Koblet til et ukryptert nettverk"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Anrop, meldinger og data er for øyeblikket mer sårbare ved bruk av SIM-kortet fra <xliff:g id="NETWORK_NAME">%1$s</xliff:g>."</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Anrop, meldinger og data er for øyeblikket mer sårbare ved bruk av SIM-kortet fra <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nNår tilkoblingen er kryptert igjen, får du et nytt varsel."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Sikkerhetsinnstillinger for mobilnettverk"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Finn ut mer"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Greit"</string> <string name="fcComplete" msgid="1080909484660507044">"Funksjonskode utført."</string> <string name="fcError" msgid="5325116502080221346">"Tilkoblingsproblem eller ugyldig funksjonskode."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Talehjelp"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Låsing"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nytt varsel"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysisk tastatur"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Sikkerhet"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ta skjermdump"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan ikke ta en skjermdump av skjermen."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Forhåndsvisning, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"lukk"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"deaktivere eller endre statusfeltet"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Lar appen deaktivere statusfeltet eller legge til og fjerne systemikoner."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"vise appen i statusfeltet"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Varsel med informasjon om rutinemodus"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterisparing er slått på"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduserer batteribruken for å forlenge batterilevetiden"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterisparing er på"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterisparing er slått på for å forlenge batterilevetiden"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparing"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterisparing er slått av"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonen har nok batteri. Funksjoner begrenses ikke lenger."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Vil du bytte til en jobbapp?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Organisasjonen din tillater bare at du ringer fra jobbapper"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organisasjonen din tillater bare at du sender meldinger fra jobbapper"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Du kan bare ringe fra den personlige Telefon-appen din. Anrop via den personlige Telefon-appen legges til i den personlige anropsloggen din."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Du kan bare sende SMS-meldinger fra den personlige Meldinger-appen din."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Bruk den personlige nettleseren"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Bruk jobbnettleseren"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Ring"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Jobb 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Felles"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Jobbprofil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privat område"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Felles"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Sensitivt varselinnhold er skjult"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Av sikkerhetsgrunner er appinnholdet skjult for skjermdelingen"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatisk tilkoblet satellitt"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfigurer opplåsingen med fingeravtrykk på nytt"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> fungerte ikke skikkelig og ble slettet. Du kan konfigurere det på nytt for å låse opp telefonen med fingeravtrykket."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> og <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> fungerte ikke skikkelig og ble slettet. Du kan konfigurere dem på nytt for å låse opp telefonen med fingeravtrykket."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfigurer ansiktslåsen på nytt"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Ansiktsmodellen din fungerte ikke skikkelig og ble slettet. Du kan konfigurere den på nytt for å låse opp telefonen med ansiktet."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Konfigurer"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ikke nå"</string> </resources> diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml index cce955fdeea5..c16a02cb1278 100644 --- a/core/res/res/values-ne/strings.xml +++ b/core/res/res/values-ne/strings.xml @@ -84,7 +84,7 @@ <string name="NetworkPreferenceSwitchTitle" msgid="1008329951315753038">"मोबाइल नेटवर्कमाथि पहुँच राख्न सकिएन"</string> <string name="NetworkPreferenceSwitchSummary" msgid="2086506181486324860">"रुचाइएको नेटवर्क परिवर्तन गरी हेर्नुहोस्। परिवर्तन गर्न ट्याप गर्नुहोस्।"</string> <string name="EmergencyCallWarningTitle" msgid="1615688002899152860">"आपत्कालीन कल सेवा अनुपलब्ध छ"</string> - <string name="EmergencyCallWarningSummary" msgid="9102799172089265268">"आपत्कालीन कलहरू गर्न मोबाइल नेटवर्क चाहिन्छ"</string> + <string name="EmergencyCallWarningSummary" msgid="9102799172089265268">"आपत्कालीन कलहरू गर्न मोबाइल नेटवर्क चाहिन्छ"</string> <string name="notification_channel_network_alert" msgid="4788053066033851841">"अलर्टहरू"</string> <string name="notification_channel_call_forward" msgid="8230490317314272406">"कल फर्वार्ड गर्ने सेवा"</string> <string name="notification_channel_emergency_callback" msgid="54074839059123159">"आपत्कालीन कलब्याक मोड"</string> @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"मोबाइल नेटवर्कको सुरक्षासम्बन्धी सेटिङ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"थप जान्नुहोस्"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"बुझेँ"</string> <string name="fcComplete" msgid="1080909484660507044">"विशेषता कोड पुरा भयो।"</string> <string name="fcError" msgid="5325116502080221346">"जडान समस्या वा अमान्य सुविधा कोड।"</string> <string name="httpErrorOk" msgid="6206751415788256357">"ठिक छ"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"आवाज सहायता"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"लकडाउन गर्नु…"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"९९९+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"नयाँ सूचना"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"फिजिकल किबोर्ड"</string> <string name="notification_channel_security" msgid="8516754650348238057">"सुरक्षा"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"स्क्रिनसट लिनुहोस्"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"डिस्प्लेको स्क्रिनसट लिन सकिन्छ।"</string> <string name="dream_preview_title" msgid="5570751491996100804">"प्रिभ्यू, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"हटाउनुहोस्"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"स्थिति पट्टिलाई अक्षम वा संशोधित गर्नुहोस्"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"स्थिति पट्टि असक्षम पार्न वा प्रणाली आइकनहरू थप्न र हटाउन एपलाई अनुमति दिन्छ।"</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"स्टाटस बार हुन दिनुहोस्"</string> @@ -479,11 +468,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> @@ -658,14 +647,14 @@ <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> <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"स्क्रिन लक प्रयोग गर्नुहोस्"</string> <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"जारी राख्न आफ्नो स्क्रिन लक हाल्नुहोस्"</string> <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"सेन्सरमा बेसरी थिच्नुहोस्"</string> - <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"फिंगरप्रिन्ट पहिचान गर्न सकिएन। फेरि प्रयास गर्नुहोस्।"</string> + <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"फिंगरप्रिन्ट मिलेन। फेरि प्रयास गर्नुहोस्।"</string> <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"फिंगरप्रिन्ट सेन्सर सफा गरेर फेरि प्रयास गर्नुहोस्"</string> <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"सेन्सर सफा गरेर फेरि प्रयास गर्नुहोस्"</string> <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"सेन्सरमा बेसरी थिच्नुहोस्"</string> @@ -677,9 +666,9 @@ <string name="fingerprint_acquired_immobile" msgid="1621891895241888048">"हरेक पटक आफ्नो औँला थोरै यताउता सार्नुहोस्"</string> <string-array name="fingerprint_acquired_vendor"> </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_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_authenticated" msgid="2024862866860283100">"फिंगरप्रिन्ट प्रमाणीकरण गरियो"</string> <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"अनुहार प्रमाणीकरण गरियो"</string> <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"अनुहार प्रमाणीकरण गरियो, कृपया पुष्टि गर्नुहोस् थिच्नुहोस्"</string> @@ -729,7 +718,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> @@ -886,30 +875,30 @@ <item msgid="4537253139152229577">"घरको फ्याक्स"</item> <item msgid="6751245029698664340">"पेजर"</item> <item msgid="1692790665884224905">"अन्य"</item> - <item msgid="6216981255272016212">"आफू अनुकूल"</item> + <item msgid="6216981255272016212">" कस्टम"</item> </string-array> <string-array name="emailAddressTypes"> <item msgid="7786349763648997741">"गृह"</item> <item msgid="435564470865989199">"काम"</item> <item msgid="4199433197875490373">"अन्य"</item> - <item msgid="3233938986670468328">"आफू अनुकूल"</item> + <item msgid="3233938986670468328">" कस्टम"</item> </string-array> <string-array name="postalAddressTypes"> <item msgid="3861463339764243038">"गृह"</item> <item msgid="5472578890164979109">"काम"</item> <item msgid="5718921296646594739">"अन्य"</item> - <item msgid="5523122236731783179">"आफू अनुकूल"</item> + <item msgid="5523122236731783179">" कस्टम"</item> </string-array> <string-array name="imAddressTypes"> <item msgid="588088543406993772">"गृह"</item> <item msgid="5503060422020476757">"काम"</item> <item msgid="2530391194653760297">"अन्य"</item> - <item msgid="7640927178025203330">"आफू अनुकूल"</item> + <item msgid="7640927178025203330">" कस्टम"</item> </string-array> <string-array name="organizationTypes"> <item msgid="6144047813304847762">"काम गर्नुहोस्"</item> <item msgid="7402720230065674193">"अन्य"</item> - <item msgid="808230403067569648">"आफू अनुकूल"</item> + <item msgid="808230403067569648">" कस्टम"</item> </string-array> <string-array name="imProtocols"> <item msgid="7535761744432206400">"AIM"</item> @@ -921,7 +910,7 @@ <item msgid="4717545739447438044">"ICQ"</item> <item msgid="8293711853624033835">"Jabber"</item> </string-array> - <string name="phoneTypeCustom" msgid="5120365721260686814">"आफू अनुकूल"</string> + <string name="phoneTypeCustom" msgid="5120365721260686814">" कस्टम"</string> <string name="phoneTypeHome" msgid="3880132427643623588">"गृह"</string> <string name="phoneTypeMobile" msgid="1178852541462086735">"मोबाइल"</string> <string name="phoneTypeWork" msgid="6604967163358864607">"काम"</string> @@ -942,24 +931,24 @@ <string name="phoneTypeWorkPager" msgid="3748332310638505234">"कार्य पेजर"</string> <string name="phoneTypeAssistant" msgid="757550783842231039">"सहायक"</string> <string name="phoneTypeMms" msgid="1799747455131365989">"MMS"</string> - <string name="eventTypeCustom" msgid="3257367158986466481">"आफू अनुकूल"</string> + <string name="eventTypeCustom" msgid="3257367158986466481">" कस्टम"</string> <string name="eventTypeBirthday" msgid="7770026752793912283">"जन्मदिन"</string> <string name="eventTypeAnniversary" msgid="4684702412407916888">"वार्षिक समारोह"</string> <string name="eventTypeOther" msgid="530671238533887997">"अन्य"</string> - <string name="emailTypeCustom" msgid="1809435350482181786">"आफू अनुकूल"</string> + <string name="emailTypeCustom" msgid="1809435350482181786">" कस्टम"</string> <string name="emailTypeHome" msgid="1597116303154775999">"गृह"</string> <string name="emailTypeWork" msgid="2020095414401882111">"काम"</string> <string name="emailTypeOther" msgid="5131130857030897465">"अन्य"</string> <string name="emailTypeMobile" msgid="787155077375364230">"मोबाइल"</string> - <string name="postalTypeCustom" msgid="5645590470242939129">"आफू अनुकूल"</string> + <string name="postalTypeCustom" msgid="5645590470242939129">" कस्टम"</string> <string name="postalTypeHome" msgid="7562272480949727912">"गृह"</string> <string name="postalTypeWork" msgid="8553425424652012826">"काम"</string> <string name="postalTypeOther" msgid="7094245413678857420">"अन्य"</string> - <string name="imTypeCustom" msgid="5653384545085765570">"आफू अनुकूल"</string> + <string name="imTypeCustom" msgid="5653384545085765570">" कस्टम"</string> <string name="imTypeHome" msgid="6996507981044278216">"गृह"</string> <string name="imTypeWork" msgid="2099668940169903123">"काम"</string> <string name="imTypeOther" msgid="8068447383276219810">"अन्य"</string> - <string name="imProtocolCustom" msgid="4437878287653764692">"आफू अनुकूल"</string> + <string name="imProtocolCustom" msgid="4437878287653764692">" कस्टम"</string> <string name="imProtocolAim" msgid="4050198236506604378">"AIM"</string> <string name="imProtocolMsn" msgid="2257148557766499232">"Windows Live"</string> <string name="imProtocolYahoo" msgid="5373338758093392231">"Yahoo"</string> @@ -971,8 +960,8 @@ <string name="imProtocolNetMeeting" msgid="4985002408136148256">"NetMeeting"</string> <string name="orgTypeWork" msgid="8684458700669564172">"काम"</string> <string name="orgTypeOther" msgid="5450675258408005553">"अन्य"</string> - <string name="orgTypeCustom" msgid="1126322047677329218">"आफू अनुकूल"</string> - <string name="relationTypeCustom" msgid="282938315217441351">"आफू अनुकूल"</string> + <string name="orgTypeCustom" msgid="1126322047677329218">" कस्टम"</string> + <string name="relationTypeCustom" msgid="282938315217441351">" कस्टम"</string> <string name="relationTypeAssistant" msgid="4057605157116589315">"सहायक"</string> <string name="relationTypeBrother" msgid="7141662427379247820">"भाइ"</string> <string name="relationTypeChild" msgid="9076258911292693601">"सन्तान"</string> @@ -987,7 +976,7 @@ <string name="relationTypeRelative" msgid="3396498519818009134">"आफन्त"</string> <string name="relationTypeSister" msgid="3721676005094140671">"बहिनी"</string> <string name="relationTypeSpouse" msgid="6916682664436031703">"पति-पत्नी"</string> - <string name="sipAddressTypeCustom" msgid="6283889809842649336">"आफू अनुकूल"</string> + <string name="sipAddressTypeCustom" msgid="6283889809842649336">" कस्टम"</string> <string name="sipAddressTypeHome" msgid="5918441930656878367">"गृह"</string> <string name="sipAddressTypeWork" msgid="7873967986701216770">"काम गर्नुहोस्"</string> <string name="sipAddressTypeOther" msgid="6317012577345187275">"अन्य"</string> @@ -1250,7 +1239,7 @@ <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"%1$s मार्फत छविलाई कैंद गर्नुहोस्"</string> <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"छविलाई कैंद गर्नुहोस्"</string> <string name="alwaysUse" msgid="3153558199076112903">"यस कार्यको लागि पूर्वनिर्धारितबाट प्रयोग गर्नुहोस्।"</string> - <string name="use_a_different_app" msgid="4987790276170972776">"फरक एप प्रयोग गर्नुहोस्"</string> + <string name="use_a_different_app" msgid="4987790276170972776">"अर्को एप प्रयोग गर्नुहोस्"</string> <string name="clearDefaultHintMsg" msgid="1325866337702524936">"प्रणाली सेटिङहरूमा पूर्वनिर्धारितलाई हटाउनुहोस् > एपहरू > डाउनलोड।"</string> <string name="chooseActivity" msgid="8563390197659779956">"एउटा कार्यको चयन गर्नुहोस्"</string> <string name="chooseUsbActivity" msgid="2096269989990986612">"USB उपकरणको लागि एउटा एप छान्नुहोस्"</string> @@ -1520,7 +1509,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> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <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> <string name="miniresolver_call" msgid="6386870060423480765">"कल गर्नुहोस्"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"कार्य प्रोफाइल ३"</string> <string name="profile_label_test" msgid="9168641926186071947">"परीक्षण"</string> <string name="profile_label_communal" msgid="8743921499944800427">"सामुदायिक"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"कार्य प्रोफाइल"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"निजी स्पेस"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"क्लोन"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"सामुदायिक"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"संवेदनशील सूचनासम्बन्धी सामग्री लुकाइएको छ"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"स्क्रिन सेयर गर्दा सुरक्षाका लागि एपमा भएको सामग्री लुकाइएको छ"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"फिंगरप्रिन्ट अनलक फेरि सेटअप गर्नुहोस्"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ले काम गरिरहेको थिएन र त्यसलाई मेटाइयो। फिंगरप्रिन्ट प्रयोग गरी आफ्नो फोन अनलक गर्न त्यसलाई फेरि सेट अप गर्नुहोस्।"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> र <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ले राम्ररी काम गरिरहेका थिएनन् र तिनलाई मेटाइयो। फिंगरप्रिन्ट प्रयोग गरी आफ्नो फोन अनलक गर्न तिनलाई फेरि सेट अप गर्नुहोस्।"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"फेस अनलक फेरि सेटअप गर्नुहोस्"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"तपाईंको फेस मोडेलले राम्ररी काम गरिरहेको थिएन र त्यसलाई मेटाइयो। अनुहार प्रयोग गरी आफ्नो फोन अनलक गर्न फेस मोडेल फेरि सेट अप गर्नुहोस्।"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"सेटअप गर्नुहोस्"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"अहिले होइन"</string> </resources> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index 40744589b83b..f8b0f9e12c36 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> na <xliff:g id="TIME_DELAY">{2}</xliff:g> seconden"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: niet doorgeschakeld"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: niet doorgeschakeld"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Beveiliging van mobiel netwerk"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Versleuteling, meldingen voor niet-versleutelde netwerken"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Toegang tot apparaat-ID gehad"</string> + <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 <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 <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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Functiecode voltooid."</string> <string name="fcError" msgid="5325116502080221346">"Verbindingsprobleem of ongeldige functiecode."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Spraakassistent"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Lockdown"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999 +"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nieuwe melding"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysiek toetsenbord"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Beveiliging"</string> @@ -2156,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Informatiemelding voor routinemodus"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterijbesparing staat aan"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Het batterijgebruik wordt beperkt om de batterijduur te verlengen"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterijbesparing staat aan"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterijbesparing is aangezet om de batterijduur te verlengen"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterijbesparing"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterijbesparing staat uit"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefoon is voldoende opgeladen. Functies worden niet meer beperkt."</string> @@ -2230,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Overschakelen naar werk-app?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Je organisatie staat je alleen toe om te bellen vanuit werk-apps"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Je organisatie staat je alleen toe om berichten te sturen vanuit werk-apps"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Je kunt alleen bellen via je persoonlijke Telefoon-app. Gesprekken vanuit je persoonlijke Telefoon-app worden aan je persoonlijke gespreksgeschiedenis toegevoegd."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Je kunt alleen sms-berichten sturen via je persoonlijke Berichten-app."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Persoonlijke browser gebruiken"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Werkbrowser gebruiken"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Bellen"</string> @@ -2413,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Werk 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Gemeenschappelijk"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Werkprofiel"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privégedeelte"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Kloon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Gemeenschappelijk"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Content van gevoelige meldingen verborgen"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"App-content verborgen voor scherm delen vanwege beveiligingsrisico\'s"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatisch verbonden met satelliet"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Ontgrendelen met vingerafdruk weer instellen"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> werkte niet goed en is verwijderd. Stel deze opnieuw in om de telefoon met je vingerafdruk te ontgrendelen."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> en <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> werkten niet goed en zijn verwijderd. Stel ze opnieuw in om de telefoon met je vingerafdruk te ontgrendelen."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Ontgrendelen via gezichtsherkenning weer instellen"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Je gezichtsmodel werkte niet goed en is verwijderd. Stel het opnieuw in om de telefoon met je gezicht te ontgrendelen."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Instellen"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Niet nu"</string> </resources> diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml index 3ffc5b9f8d83..9abed2b264f4 100644 --- a/core/res/res/values-or/strings.xml +++ b/core/res/res/values-or/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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> + <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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ଅଧିକ ଜାଣନ୍ତୁ"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ବୁଝିଗଲି"</string> <string name="fcComplete" msgid="1080909484660507044">"ଫିଚର୍ କୋଡ୍ ସମ୍ପୂର୍ଣ୍ଣ।"</string> <string name="fcError" msgid="5325116502080221346">"ସଂଯୋଗରେ ସମସ୍ୟା କିମ୍ୱା ଅମାନ୍ୟ ଫିଚର୍ କୋଡ୍।"</string> <string name="httpErrorOk" msgid="6206751415788256357">"ଠିକ ଅଛି"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ଭଏସ୍ ସହାୟକ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"ଲକ୍ କରନ୍ତୁ"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"ନୂଆ ବିଜ୍ଞପ୍ତି"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ଫିଜିକଲ୍ କୀ’ବୋର୍ଡ"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ସୁରକ୍ଷା"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <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> <string name="miniresolver_call" msgid="6386870060423480765">"କଲ କରନ୍ତୁ"</string> @@ -2413,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"ୱାର୍କ 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ଟେଷ୍ଟ"</string> <string name="profile_label_communal" msgid="8743921499944800427">"କମ୍ୟୁନାଲ"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ୱାର୍କ ପ୍ରୋଫାଇଲ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ପ୍ରାଇଭେଟ ସ୍ପେସ"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"କ୍ଲୋନ"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"କମ୍ୟୁନାଲ"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"ସମ୍ୱେଦନଶୀଳ ବିଜ୍ଞପ୍ତି ବିଷୟବସ୍ତୁକୁ ଲୁଚାଯାଇଛି"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ସୁରକ୍ଷା ପାଇଁ ସ୍କ୍ରିନ ସେୟାରରୁ ଆପ ବିଷୟବସ୍ତୁକୁ ଲୁଚାଯାଇଛି"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ସାଟେଲାଇଟ ସହ ସ୍ୱତଃ କନେକ୍ଟ ହୋଇଛି"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ଫିଙ୍ଗରପ୍ରିଣ୍ଟ ଅନଲକ ପୁଣି ସେଟ ଅପ କରନ୍ତୁ"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ସଠିକ ଭାବେ କାମ କରୁନାହିଁ ଏବଂ ଏହାକୁ ଡିଲିଟ କରାଯାଇଛି। ଟିପଚିହ୍ନ ମାଧ୍ୟମରେ ଆପଣଙ୍କ ଫୋନକୁ ଅନଲକ କରିବାକୁ ଏହାକୁ ପୁଣି ସେଟ ଅପ କରନ୍ତୁ।"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ଏବଂ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ସଠିକ ଭାବେ କାମ କରୁନାହିଁ ଏବଂ ଏଗୁଡ଼ିକୁ ଡିଲିଟ କରାଯାଇଛି। ଆପଣଙ୍କ ଟିପଚିହ୍ନ ମାଧ୍ୟମରେ ଆପଣଙ୍କ ଫୋନକୁ ଅନଲକ କରିବାକୁ ଏଗୁଡ଼ିକୁ ପୁଣି ସେଟ ଅପ କରନ୍ତୁ।"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ଫେସ୍ ଅନଲକ୍ ପୁଣି ସେଟ୍ ଅପ୍ କରନ୍ତୁ"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"ଆପଣଙ୍କ ଫେସ ମଡେଲ ସଠିକ ଭାବେ କାମ କରୁନାହିଁ ଏବଂ ଏହାକୁ ଡିଲିଟ କରାଯାଇଛି। ଫେସ ମାଧ୍ୟମରେ ଆପଣଙ୍କ ଫୋନକୁ ଅନଲକ କରିବାକୁ ଏହାକୁ ପୁଣି ସେଟ ଅପ କରନ୍ତୁ।"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"ସେଟ ଅପ କରନ୍ତୁ"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ବର୍ତ୍ତମାନ ନୁହେଁ"</string> </resources> diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml index 42a229eb8ea3..64b326c9f227 100644 --- a/core/res/res/values-pa/strings.xml +++ b/core/res/res/values-pa/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਵਿਲੱਖਣ ਆਈਡੀ (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> ਸਿਮ ਦੀ ਵਰਤੋਂ ਕਰ ਕੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਦੀ ਵਿਲੱਖਣ ਆਈਡੀ (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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ਮੋਬਾਈਲ ਨੈੱਟਵਰਕ ਸੁਰੱਖਿਆ ਸੰਬੰਧੀ ਸੈਟਿੰਗਾਂ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ਹੋਰ ਜਾਣੋ"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ਸਮਝ ਲਿਆ"</string> <string name="fcComplete" msgid="1080909484660507044">"ਵਿਸ਼ੇਸ਼ਤਾ ਕੋਡ ਪੂਰਾ।"</string> <string name="fcError" msgid="5325116502080221346">"ਕਨੈਕਸ਼ਨ ਸਮੱਸਿਆ ਜਾਂ ਅਵੈਧ ਵਿਸ਼ੇਸ਼ਤਾ ਕੋਡ।"</string> <string name="httpErrorOk" msgid="6206751415788256357">"ਠੀਕ ਹੈ"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ਅਵਾਜ਼ੀ ਸਹਾਇਕ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"ਲਾਕਡਾਊਨ"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"ਨਵੀਂ ਸੂਚਨਾ"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡ"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ਸੁਰੱਖਿਆ"</string> @@ -328,7 +318,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> @@ -843,8 +833,8 @@ <string name="permdesc_writeVerificationStateE2eeContactKeys" msgid="8453156829747427041">"ਐਪ ਨੂੰ ਹੋਰ ਐਪਾਂ ਦੀ ਮਲਕੀਅਤ ਵਾਲੀਆਂ E2EE ਸੰਪਰਕ ਕੁੰਜੀਆਂ ਦੀ ਪੁਸ਼ਟੀਕਰਨ ਸਥਿਤੀਆਂ ਨੂੰ ਅੱਪਡੇਟ ਕਰਨ ਦੀ ਆਗਿਆ ਦਿੰਦਾ ਹੈ"</string> <string name="policylab_limitPassword" msgid="4851829918814422199">"ਪਾਸਵਰਡ ਨਿਯਮ ਸੈੱਟ ਕਰੋ"</string> <string name="policydesc_limitPassword" msgid="4105491021115793793">"ਸਕ੍ਰੀਨ ਲਾਕ ਪਾਸਵਰਡਾਂ ਅਤੇ ਪਿੰਨ ਵਿੱਚ ਆਗਿਆ ਦਿੱਤੀ ਲੰਮਾਈ ਅਤੇ ਅੱਖਰਾਂ ਤੇ ਨਿਯੰਤਰਣ ਪਾਓ।"</string> - <string name="policylab_watchLogin" msgid="7599669460083719504">"ਸਕ੍ਰੀਨ ਅਣਲਾਕ ਕਰਨ ਦੀਆਂ ਕੋਸ਼ਿਸ਼ਾਂ \'ਤੇ ਨਿਗਰਾਨੀ ਰੱਖੋ"</string> - <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਟੈਬਲੈੱਟ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਟੈਬਲੈੱਟ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ, ਜੇਕਰ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string> + <string name="policylab_watchLogin" msgid="7599669460083719504">"ਸਕ੍ਰੀਨ ਅਣਲਾਕ ਕਰਨ ਦੀਆਂ ਕੋਸ਼ਿਸ਼ਾਂ \'ਤੇ ਨਿਗਰਾਨੀ ਰੱਖਣਾ"</string> + <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰਨਾ ਅਤੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਵਾਰ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਜਾਣ \'ਤੇ ਟੈਬਲੈੱਟ ਨੂੰ ਲਾਕ ਕਰਨਾ ਜਾਂ ਟੈਬਲੈੱਟ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਉਣਾ।"</string> <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਹੋਏ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦਾ ਨਿਰੀਖਣ ਕਰੋ ਅਤੇ ਆਪਣੇ Android TV ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਆਪਣੇ Android TV ਡੀਵਾਈਸ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ, ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ।"</string> <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਸੰਖਿਆ ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ ਅਤੇ ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ, ਤਾਂ ਵਾਹਨ ਆਡੀਓ ਸਿਸਟਮ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਵਾਹਨ ਆਡੀਓ ਸਿਸਟਮ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"ਸਕ੍ਰੀਨ ਨੂੰ ਅਣਲਾਕ ਕਰਦੇ ਸਮੇਂ ਟਾਈਪ ਕੀਤੇ ਗਲਤ ਪਾਸਵਰਡਾਂ ਦੀ ਗਿਣਤੀ ਦੀ ਨਿਗਰਾਨੀ ਕਰੋ ਅਤੇ ਜੇ ਬਹੁਤ ਜ਼ਿਆਦਾ ਗਲਤ ਪਾਸਵਰਡ ਟਾਈਪ ਕੀਤੇ ਹਨ, ਤਾਂ ਫ਼ੋਨ ਨੂੰ ਲਾਕ ਕਰੋ ਜਾਂ ਫ਼ੋਨ ਦਾ ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਓ।"</string> @@ -856,8 +846,8 @@ <string name="policydesc_resetPassword" msgid="4626419138439341851">"ਸਕ੍ਰੀਨ ਲਾਕ ਬਦਲੋ।"</string> <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="policylab_wipeData" msgid="1359485247727537311">"ਸਾਰਾ ਡਾਟਾ ਮਿਟਾਉਣਾ"</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> @@ -1249,7 +1239,7 @@ <string name="whichImageCaptureApplicationNamed" msgid="8820702441847612202">"%1$s ਨਾਲ ਚਿਤਰ ਕੈਪਚਰ ਕਰੋ"</string> <string name="whichImageCaptureApplicationLabel" msgid="6505433734824988277">"ਚਿਤਰ ਕੈਪਚਰ ਕਰੋ"</string> <string name="alwaysUse" msgid="3153558199076112903">"ਇਸ ਕਾਰਵਾਈ ਲਈ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਵਰਤੋ।"</string> - <string name="use_a_different_app" msgid="4987790276170972776">"ਇੱਕ ਵੱਖਰਾ ਖਾਤਾ ਵਰਤੋ"</string> + <string name="use_a_different_app" msgid="4987790276170972776">"ਕੋਈ ਵੱਖਰੀ ਐਪ ਵਰਤੋ"</string> <string name="clearDefaultHintMsg" msgid="1325866337702524936">"ਸਿਸਟਮ ਸੈਟਿੰਗਾਂ > ਐਪਾਂ > ਡਾਊਨਲੋਡ ਕੀਤਿਆਂ ਵਿੱਚ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਹਟਾਓ।"</string> <string name="chooseActivity" msgid="8563390197659779956">"ਇੱਕ ਕਾਰਵਾਈ ਚੁਣੋ"</string> <string name="chooseUsbActivity" msgid="2096269989990986612">"USB ਡੀਵਾਈਸ ਲਈ ਇੱਕ ਐਪ ਚੁਣੋ"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"ਤੁਸੀਂ ਸਿਰਫ਼ ਆਪਣੀ ਨਿੱਜੀ ਫ਼ੋਨ ਐਪ ਤੋਂ ਫ਼ੋਨ ਕਾਲਾਂ ਕਰ ਸਕਦੇ ਹੋ। ਨਿੱਜੀ ਫ਼ੋਨ ਤੋਂ ਕੀਤੀਆਂ ਕਾਲਾਂ ਤੁਹਾਡੇ ਨਿੱਜੀ ਕਾਲ ਇਤਿਹਾਸ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤੀਆਂ ਜਾਣਗੀਆਂ।"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"ਤੁਸੀਂ ਸਿਰਫ਼ ਆਪਣੀ ਨਿੱਜੀ ਸੁਨੇਹਾ ਐਪ ਤੋਂ SMS ਸੁਨੇਹੇ ਭੇਜ ਸਕਦੇ ਹੋ।"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ਨਿੱਜੀ ਬ੍ਰਾਊਜ਼ਰ ਵਰਤੋ"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"ਕੰਮ ਸੰਬੰਧੀ ਬ੍ਰਾਊਜ਼ਰ ਵਰਤੋ"</string> <string name="miniresolver_call" msgid="6386870060423480765">"ਕਾਲ ਕਰੋ"</string> @@ -2406,15 +2396,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"ਕੀ-ਬੋਰਡ ਦਾ ਖਾਕਾ <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g> \'ਤੇ ਸੈੱਟ ਹੈ… ਬਦਲਣ ਲਈ ਟੈਪ ਕਰੋ।"</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"ਭੌਤਿਕ ਕੀ-ਬੋਰਡਾਂ ਦਾ ਸੰਰੂਪਣ ਕੀਤਾ ਗਿਆ"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"ਕੀ-ਬੋਰਡਾਂ ਨੂੰ ਦੇਖਣ ਲਈ ਟੈਪ ਕਰੋ"</string> - <string name="profile_label_private" msgid="6463418670715290696">"ਨਿੱਜੀ"</string> + <string name="profile_label_private" msgid="6463418670715290696">"ਪ੍ਰਾਈਵੇਟ"</string> <string name="profile_label_clone" msgid="769106052210954285">"ਕਲੋਨ"</string> <string name="profile_label_work" msgid="3495359133038584618">"ਕਾਰਜ"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"ਕੰਮ ਸੰਬੰਧੀ 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"ਕੰਮ ਸੰਬੰਧੀ 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ਜਾਂਚ"</string> <string name="profile_label_communal" msgid="8743921499944800427">"ਭਾਈਚਾਰਕ"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ਕਲੋਨ"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ਭਾਈਚਾਰਕ"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"ਲੁਕੀ ਹੋਈ ਸੰਵੇਦਨਸ਼ੀਲ ਸੂਚਨਾ ਸਮੱਗਰੀ"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ਐਪ ਸਮੱਗਰੀ ਨੂੰ ਸੁਰੱਖਿਆ ਲਈ ਸਕ੍ਰੀਨ ਸਾਂਝਾਕਰਨ ਤੋਂ ਲੁਕਾਇਆ ਗਿਆ ਹੈ"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"ਸੈਟੇਲਾਈਟ ਨਾਲ ਸਵੈ-ਕਨੈਕਟ ਹੋਇਆ"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ਫਿੰਗਰਪ੍ਰਿੰਟ ਅਣਲਾਕ ਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ਚੰਗੀ ਤਰ੍ਹਾਂ ਕੰਮ ਨਹੀਂ ਕਰ ਰਿਹਾ ਸੀ ਅਤੇ ਉਸਨੂੰ ਮਿਟਾਇਆ ਗਿਆ ਸੀ। ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਫਿੰਗਰਪ੍ਰਿੰਟ ਨਾਲ ਅਣਲਾਕ ਕਰਨ ਲਈ ਇਸਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ।"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ਅਤੇ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ਚੰਗੀ ਤਰ੍ਹਾਂ ਕੰਮ ਨਹੀਂ ਕਰ ਰਹੇ ਸੀ ਅਤੇ ਉਨ੍ਹਾਂ ਨੂੰ ਮਿਟਾਇਆ ਗਿਆ ਸੀ। ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ ਨਾਲ ਅਣਲਾਕ ਕਰਨ ਲਈ ਇਨ੍ਹਾਂ ਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ।"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ਫ਼ੇਸ ਅਣਲਾਕ ਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"ਤੁਹਾਡਾ ਚਿਹਰੇ ਦਾ ਮਾਡਲ ਚੰਗੀ ਤਰ੍ਹਾਂ ਕੰਮ ਨਹੀਂ ਕਰ ਰਿਹਾ ਸੀ ਅਤੇ ਉਸਨੂੰ ਮਿਟਾਇਆ ਗਿਆ ਸੀ। ਆਪਣੇ ਫ਼ੋਨ ਨੂੰ ਚਿਹਰੇ ਨਾਲ ਅਣਲਾਕ ਕਰਨ ਲਈ ਇਸਦਾ ਦੁਬਾਰਾ ਸੈੱਟਅੱਪ ਕਰੋ।"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"ਸੈੱਟਅੱਪ ਕਰੋ"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ਹੁਣੇ ਨਹੀਂ"</string> </resources> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index 2dccba91d80c..9cc99166d98f 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -155,31 +155,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> sekundach"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: nieprzekierowane"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Bezpieczeństwo sieci komórkowej"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Szyfrowanie, powiadomienia dotyczące niezaszyfrowanych sieci"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Uzyskano dostęp do identyfikatora urządzenia"</string> + <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 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">"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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Wykonano kod funkcji."</string> <string name="fcError" msgid="5325116502080221346">"Problem z połączeniem lub nieprawidłowy kod funkcji."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -297,6 +285,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Asystent głosowy"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blokada"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nowe powiadomienie"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Klawiatura fizyczna"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Bezpieczeństwo"</string> @@ -373,8 +363,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Robienie zrzutu ekranu"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Może robić zrzuty ekranu wyświetlacza."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Podgląd, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"zamknij"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"wyłączanie lub zmienianie paska stanu"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Pozwala aplikacji na wyłączanie paska stanu oraz dodawanie i usuwanie ikon systemowych."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"działanie jako pasek stanu"</string> @@ -2159,6 +2148,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Powiadomienie z informacją o trybie rutynowym"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Oszczędzanie baterii jest włączone"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Ograniczam wykorzystanie baterii, aby przedłużyć jej żywotność"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Oszczędzanie baterii jest włączone"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Oszczędzanie baterii jest włączone, aby wydłużyć jej żywotność"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Oszczędzanie baterii"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Oszczędzanie baterii zostało wyłączone"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon jest wystarczająco naładowany. Funkcje nie są już ograniczone."</string> @@ -2233,10 +2224,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Przełączyć na aplikację służbową?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Twoja organizacja zezwala na nawiązywanie połączeń tylko z aplikacji służbowych"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Twoja organizacja zezwala na wysyłanie wiadomości tylko z aplikacji służbowych"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Połączenia telefoniczne możesz wykonywać wyłącznie za pomocą osobistej aplikacji Telefon. Takie połączenia zostaną dodane do Twojej osobistej historii połączeń."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Wiadomości SMS możesz wysyłać wyłącznie z osobistej aplikacji Wiadomości."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Użyj przeglądarki osobistej"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Użyj przeglądarki służbowej"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Zadzwoń"</string> @@ -2416,6 +2405,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Służbowy 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Testowy"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Wspólny"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil służbowy"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Przestrzeń prywatna"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Wspólny"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Treść poufnego powiadomienia została ukryta"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Ze względów bezpieczeństwa zawartość aplikacji jest niewidoczna podczas udostępniania ekranu"</string> @@ -2424,4 +2417,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Skonfiguruj ponownie odblokowywanie odciskiem palca"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Odcisk palca <xliff:g id="FINGERPRINT">%s</xliff:g> nie sprawdzał się dobrze i został usunięty. Skonfiguruj go ponownie, aby odblokowywać telefon odciskiem palca."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Odciski palca <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> i <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nie sprawdzały się dobrze i zostały usunięte. Skonfiguruj je ponownie, aby odblokowywać telefon odciskiem palca."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Skonfiguruj ponownie rozpoznawanie twarzy"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Twój model twarzy nie sprawdzał się dobrze i został usunięty. Skonfiguruj go ponownie, aby odblokowywać telefon za pomocą skanu twarzy."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Skonfiguruj"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Nie teraz"</string> </resources> diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml index 8a0371634255..e3b49a0b399b 100644 --- a/core/res/res/values-pt-rBR/strings.xml +++ b/core/res/res/values-pt-rBR/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Segurança de rede móvel"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Criptografia, notificações para redes não criptografadas"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID do dispositivo acessado"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Às <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, uma rede próxima registrou o ID exclusivo do seu dispositivo (IMSI ou IMEI) enquanto usava seu chip da <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>, uma rede próxima registrou o ID exclusivo do seu dispositivo (IMSI ou IMEI) usando seu chip da <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nIsso significa que sua localização, atividade ou identidade foram registradas. Essa é uma prática comum, mas pode ser um problema para pessoas preocupadas com a privacidade."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectado à rede criptografada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"A conexão do chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g> está mais segura agora"</string> + <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 da rede móvel"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saiba mais"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendi"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de recurso concluído."</string> <string name="fcError" msgid="5325116502080221346">"Problema de conexão ou código de recurso inválido."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ajuda de voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueio total"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fazer uma captura de tela"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pode fazer uma captura de tela."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Visualização, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"dispensar"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"desativar ou modificar a barra de status"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite que o app desative a barra de status ou adicione e remova ícones do sistema."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"ser a barra de status"</string> @@ -1224,7 +1213,7 @@ <string name="not_selected" msgid="410652016565864475">"não selecionado"</string> <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{1 de {max} estrelas}one{# de {max} estrelas}many{# de {max} estrelas}other{# de {max} estrelas}}"</string> <string name="in_progress" msgid="2149208189184319441">"em andamento"</string> - <string name="whichApplication" msgid="5432266899591255759">"Complete a ação usando"</string> + <string name="whichApplication" msgid="5432266899591255759">"Concluir a ação usando"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Concluir a ação usando %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Concluir ação"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Abrir com"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informação do modo rotina"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economia de bateria ativada"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzindo o uso da bateria para prolongar a duração dela"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"A Economia de bateria está ativada"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"A Economia de bateria está ativada para prolongar a duração da carga"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economia de bateria"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Economia de bateria\" desativada"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Não há carga suficiente no smartphone. Os recursos não estão mais restritos."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Trocar para o app de trabalho?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Sua organização só permite fazer ligações usando apps de trabalho"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Sua organização só permite o envio de mensagens usando apps de trabalho"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"É possível fazer ligações apenas usando seu app Telefone pessoal. Essas ligações telefônicas serão adicionadas ao seu histórico pessoal."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"É possível enviar mensagens SMS apenas usando seu app Mensagens pessoal."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Usar o navegador pessoal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Usar o navegador de trabalho"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Ligar"</string> @@ -2408,15 +2397,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Layout do teclado definido como <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Toque para mudar."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Teclados físicos configurados"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Toque para conferir os teclados"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Particular"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Privado"</string> <string name="profile_label_clone" msgid="769106052210954285">"Clone"</string> <string name="profile_label_work" msgid="3495359133038584618">"Trabalho"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Trabalho 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Teste"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Público"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Público"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Conteúdo de notificação sensível oculto"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conteúdo do app oculto no compartilhamento de tela por motivos de segurança"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conectado automaticamente ao satélite"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configurar o Desbloqueio por impressão digital de novo"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A impressão digital <xliff:g id="FINGERPRINT">%s</xliff:g> não estava funcionando bem e foi excluída. Configure de novo para desbloquear o smartphone com a impressão digital."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"As impressões digitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam funcionando bem e foram excluídas. Configure de novo para desbloquear o smartphone com a impressão digital."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Configure o Desbloqueio facial de novo"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Seu modelo de rosto não estava funcionando bem e foi excluído. Configure de novo para desbloquear o smartphone com o rosto."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configuração"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Agora não"</string> </resources> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 5b130bca46d4..d8c1f72fecb5 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não reencaminhado"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não reencaminhado"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Segurança de redes móveis"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Encriptação e notificações para redes não encriptadas"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID do dispositivo acedido"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"À(s) <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, uma rede próxima registou o ID exclusivo do seu dispositivo (IMSI ou IMEI) enquanto usava o seu SIM <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>, uma rede próxima registou o ID exclusivo do seu dispositivo (IMSI ou IMEI) enquanto usava o seu SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nIsto significa que a sua localização, atividade ou identidade foram registadas. Esta é uma prática comum, mas pode ser um problema para pessoas preocupadas com a privacidade."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Ligação estabelecida à rede <xliff:g id="NETWORK_NAME">%1$s</xliff:g> encriptada"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Agora, a ligação ao SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> é mais segura"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Ligação estabelecida a uma rede não encriptada"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"As chamadas, as mensagens e os dados são atualmente mais vulneráveis quando usa o SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"As chamadas, as mensagens e os dados são atualmente mais vulneráveis quando usa o SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nQuando a sua ligação for novamente encriptada, recebe outra notificação."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Definições da segurança de redes móveis"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saber mais"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de funcionalidade completo."</string> <string name="fcError" msgid="5325116502080221346">"Problema de ligação ou código de funcionalidade inválido."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Assist. de voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloquear"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string> @@ -339,7 +329,7 @@ <string name="permgroupdesc_storage" msgid="5378659041354582769">"aceder aos ficheiros no seu dispositivo"</string> <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Música e áudio"</string> <string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"aceder a música e áudio no seu dispositivo"</string> - <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"fotos e vídeos"</string> + <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Fotos e vídeos"</string> <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"aceder a fotos e vídeos no seu dispositivo"</string> <string name="permgrouplab_microphone" msgid="2480597427667420076">"Microfone"</string> <string name="permgroupdesc_microphone" msgid="1047786732792487722">"gravar áudio"</string> @@ -1401,7 +1391,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> @@ -2157,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informações do Modo rotina"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Poupança de bateria ativada"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzir a utilização da bateria para prolongar a autonomia da mesma"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Poupança de bateria ativada"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"A Poupança de bateria está ativada para prolongar a autonomia da bateria"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Poupança de bateria"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"A Poupança de bateria está desativada"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"O telemóvel tem carga suficiente. As funcionalidades já não estão restritas."</string> @@ -2231,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Mudar para a app de trabalho?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"A sua organização só lhe permite fazer chamadas a partir de apps de trabalho"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"A sua organização só lhe permite enviar mensagens a partir de apps de trabalho"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Só pode fazer chamadas telefónicas a partir da sua app Telefone pessoal. As chamadas feitas com a app Telefone pessoal são adicionadas ao seu histórico de chamadas pessoal."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Só pode enviar mensagens SMS a partir da sua app Mensagens pessoal."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Usar navegador pessoal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Usar navegador de trabalho"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Ligar"</string> @@ -2414,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Teste"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Comum"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Comum"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Conteúdo das notificações sensíveis ocultado"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conteúdo da app ocultado da partilha de ecrã por motivos de segurança"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Ligação de satélite estabelecida automaticamente"</string> @@ -2423,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configure o Desbloqueio por impressão digital novamente"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A <xliff:g id="FINGERPRINT">%s</xliff:g> não estava a funcionar bem e foi eliminada. Configure-a novamente para desbloquear o telemóvel com a impressão digital."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"A <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam a funcionar bem e foram eliminadas. Configure-as novamente para desbloquear o telemóvel com a sua impressão digital."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Configure o Desbloqueio facial novamente"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"O seu modelo de rosto não estava a funcionar bem e foi eliminado. Configure-o novamente para desbloquear o telemóvel com o rosto."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configurar"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Agora não"</string> </resources> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 8a0371634255..e3b49a0b399b 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> após <xliff:g id="TIME_DELAY">{2}</xliff:g> segundos"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Não encaminhado"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Segurança de rede móvel"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Criptografia, notificações para redes não criptografadas"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"ID do dispositivo acessado"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Às <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, uma rede próxima registrou o ID exclusivo do seu dispositivo (IMSI ou IMEI) enquanto usava seu chip da <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>, uma rede próxima registrou o ID exclusivo do seu dispositivo (IMSI ou IMEI) usando seu chip da <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nIsso significa que sua localização, atividade ou identidade foram registradas. Essa é uma prática comum, mas pode ser um problema para pessoas preocupadas com a privacidade."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectado à rede criptografada <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"A conexão do chip da <xliff:g id="NETWORK_NAME">%1$s</xliff:g> está mais segura agora"</string> + <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 da rede móvel"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Saiba mais"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Entendi"</string> <string name="fcComplete" msgid="1080909484660507044">"Código de recurso concluído."</string> <string name="fcError" msgid="5325116502080221346">"Problema de conexão ou código de recurso inválido."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ajuda de voz"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Bloqueio total"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nova notificação"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Teclado físico"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Segurança"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fazer uma captura de tela"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Pode fazer uma captura de tela."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Visualização, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"dispensar"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"desativar ou modificar a barra de status"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite que o app desative a barra de status ou adicione e remova ícones do sistema."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"ser a barra de status"</string> @@ -1224,7 +1213,7 @@ <string name="not_selected" msgid="410652016565864475">"não selecionado"</string> <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{1 de {max} estrelas}one{# de {max} estrelas}many{# de {max} estrelas}other{# de {max} estrelas}}"</string> <string name="in_progress" msgid="2149208189184319441">"em andamento"</string> - <string name="whichApplication" msgid="5432266899591255759">"Complete a ação usando"</string> + <string name="whichApplication" msgid="5432266899591255759">"Concluir a ação usando"</string> <string name="whichApplicationNamed" msgid="6969946041713975681">"Concluir a ação usando %1$s"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"Concluir ação"</string> <string name="whichViewApplication" msgid="5733194231473132945">"Abrir com"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificação de informação do modo rotina"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economia de bateria ativada"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Reduzindo o uso da bateria para prolongar a duração dela"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"A Economia de bateria está ativada"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"A Economia de bateria está ativada para prolongar a duração da carga"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economia de bateria"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"\"Economia de bateria\" desativada"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Não há carga suficiente no smartphone. Os recursos não estão mais restritos."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Trocar para o app de trabalho?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Sua organização só permite fazer ligações usando apps de trabalho"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Sua organização só permite o envio de mensagens usando apps de trabalho"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"É possível fazer ligações apenas usando seu app Telefone pessoal. Essas ligações telefônicas serão adicionadas ao seu histórico pessoal."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"É possível enviar mensagens SMS apenas usando seu app Mensagens pessoal."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Usar o navegador pessoal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Usar o navegador de trabalho"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Ligar"</string> @@ -2408,15 +2397,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Layout do teclado definido como <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Toque para mudar."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Teclados físicos configurados"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Toque para conferir os teclados"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Particular"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Privado"</string> <string name="profile_label_clone" msgid="769106052210954285">"Clone"</string> <string name="profile_label_work" msgid="3495359133038584618">"Trabalho"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Trabalho 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Trabalho 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Teste"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Público"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Perfil de trabalho"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Espaço privado"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Público"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Conteúdo de notificação sensível oculto"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conteúdo do app oculto no compartilhamento de tela por motivos de segurança"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Conectado automaticamente ao satélite"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configurar o Desbloqueio por impressão digital de novo"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"A impressão digital <xliff:g id="FINGERPRINT">%s</xliff:g> não estava funcionando bem e foi excluída. Configure de novo para desbloquear o smartphone com a impressão digital."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"As impressões digitais <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> e <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> não estavam funcionando bem e foram excluídas. Configure de novo para desbloquear o smartphone com a impressão digital."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Configure o Desbloqueio facial de novo"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Seu modelo de rosto não estava funcionando bem e foi excluído. Configure de novo para desbloquear o smartphone com o rosto."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configuração"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Agora não"</string> </resources> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index ac33fa12f06e..7de995219497 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -154,31 +154,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> după <xliff:g id="TIME_DELAY">{2}</xliff:g> secunde"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neredirecționat"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: neredirecționat"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Securitatea rețelelor mobile"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Criptare, notificări pentru rețele necriptate"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"S-a accesat ID-ul dispozitivului"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"La <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, o rețea din apropiere a înregistrat ID-ul unic al dispozitivului tău (IMSI sau IMEI) în timp ce se folosea cardul tău SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"La <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, o rețea din apropiere a înregistrat ID-ul unic al dispozitivului tău (IMSI sau IMEI) în timp ce se folosea cardul tău SIM <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nAceasta înseamnă că locația, activitatea sau identitatea au fost înregistrate. Această practică este frecventă, dar poate fi o problemă pentru persoanele preocupate de confidențialitate."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Conectat la rețeaua criptată <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Conexiunea la cardul SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g> este mai sigură acum"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Conectat la rețeaua necriptată"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Apelurile, mesajele și datele sunt acum mai vulnerabile în timp ce este folosit cardul SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Apelurile, mesajele și datele sunt acum mai vulnerabile în timp ce este folosit cardul SIM <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nCând conexiunea este din nou criptată, vei primi altă notificare."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Setări de securitate a rețelelor mobile"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Află mai multe"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Cod de funcție complet."</string> <string name="fcError" msgid="5325116502080221346">"Problemă de conectare sau cod de funcție nevalid."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Asistent vocal"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blocare strictă"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"˃999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Notificare nouă"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastatură fizică"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Securitate"</string> @@ -372,8 +362,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Fă o captură de ecran"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Poate face o captură de ecran."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Previzualizare, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"închide"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"dezactivare sau modificare bare de stare"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Permite aplicației să dezactiveze bara de stare sau să adauge și să elimine pictograme de sistem."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"să fie bara de stare"</string> @@ -2158,6 +2147,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notificare pentru informații despre modul Rutină"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Economisirea bateriei este activată"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Se reduce utilizarea bateriei pentru a-i extinde autonomia"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Economisirea bateriei este activată"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Economisirea bateriei este activată pentru a extinde autonomia"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Economisirea bateriei"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Economisirea bateriei a fost dezactivată"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonul este încărcat suficient. Funcțiile nu mai sunt limitate."</string> @@ -2232,10 +2223,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Comuți la aplicația pentru lucru?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Organizația îți permite să inițiezi apeluri numai din aplicațiile pentru lucru"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organizația îți permite să trimiți mesaje numai din aplicațiile pentru lucru"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Poți să inițiezi apeluri telefonice numai din aplicația Telefon personală. Apelurile inițiate cu aplicația Telefon personală vor fi adăugate la istoricul apelurilor personale."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Poți să trimiți mesaje SMS numai din aplicația personală pentru mesaje."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Folosește browserul personal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Folosește browserul de serviciu"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Apelează"</string> @@ -2415,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Serviciu 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Comun"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profil de serviciu"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Spațiu privat"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clonă"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Comun"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Conținutul sensibil din notificări a fost ascuns"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Conținutul aplicației este ascuns de permiterea accesului la ecran din motive de securitate"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"S-a conectat automat la satelit"</string> @@ -2424,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Configurează din nou Deblocarea cu amprenta"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> nu funcționa bine și s-a șters. Configureaz-o din nou pentru a-ți debloca telefonul cu amprenta."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> și <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nu funcționau bine și s-au șters. Configurează-le din nou pentru a-ți debloca telefonul cu amprenta."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Reconfigurează Deblocarea facială"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Modelul tău facial nu funcționa bine și s-a șters. Configurează-l din nou pentru a-ți debloca telefonul folosindu-ți chipul."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Configurează"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Nu acum"</string> </resources> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index 63992d671bd3..d999bf5b2050 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -155,31 +155,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"ОК"</string> <string name="fcComplete" msgid="1080909484660507044">"Код функции выполнен."</string> <string name="fcError" msgid="5325116502080221346">"Неполадки подключения или неверный код функции."</string> <string name="httpErrorOk" msgid="6206751415788256357">"ОК"</string> @@ -297,6 +285,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Аудиоподсказки"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Блокировка входа"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">">999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Новое уведомление"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физическая клавиатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Безопасность"</string> @@ -373,8 +363,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Создавать скриншоты"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Создавать снимки экрана."</string> <string name="dream_preview_title" msgid="5570751491996100804">"<xliff:g id="DREAM_NAME">%1$s</xliff:g>: предпросмотр"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"закрыть"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"Отключение/изменение строки состояния"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Приложение сможет отключать строку состояния, а также добавлять и удалять системные значки."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"Замена строки состояния"</string> @@ -681,7 +670,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> @@ -1911,7 +1900,7 @@ <string name="managed_profile_label_badge_2" msgid="5673187309555352550">"Задача 2: <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="managed_profile_label_badge_3" msgid="6882151970556391957">"Задача 3: <xliff:g id="LABEL">%1$s</xliff:g>"</string> <string name="clone_profile_label_badge" msgid="1871997694718793964">"Клонировать <xliff:g id="LABEL">%1$s</xliff:g>"</string> - <string name="private_profile_label_badge" msgid="1712086003787839183">"<xliff:g id="LABEL">%1$s</xliff:g> (личный профиль)"</string> + <string name="private_profile_label_badge" msgid="1712086003787839183">"<xliff:g id="LABEL">%1$s</xliff:g> (частный профиль)"</string> <string name="lock_to_app_unlock_pin" msgid="3890940811866290782">"Запрашивать PIN-код"</string> <string name="lock_to_app_unlock_pattern" msgid="2694204070499712503">"Запрашивать графический ключ"</string> <string name="lock_to_app_unlock_password" msgid="9126722403506560473">"Запрашивать пароль"</string> @@ -2012,7 +2001,7 @@ <string name="work_mode_emergency_call_button" msgid="6818855962881612322">"Экстренный вызов"</string> <string name="set_up_screen_lock_title" msgid="8346083801616474030">"Настройте блокировку экрана"</string> <string name="set_up_screen_lock_action_label" msgid="2687634803649209367">"Настроить блокировку экрана"</string> - <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Чтобы использовать личное пространство, настройте блокировку экрана на этом устройстве."</string> + <string name="private_space_set_up_screen_lock_message" msgid="1109956797005149814">"Чтобы использовать частное пространство, настройте блокировку экрана на этом устройстве."</string> <string name="app_blocked_title" msgid="7353262160455028160">"Приложение недоступно"</string> <string name="app_blocked_message" msgid="542972921087873023">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" сейчас недоступно."</string> <string name="app_streaming_blocked_title" msgid="6090945835898766139">"Недоступно: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string> @@ -2159,6 +2148,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> + <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> @@ -2233,10 +2224,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Вы можете звонить только из личного приложения \"Телефон\". Совершенные из него вызовы будут сохранены в личном журнале звонков."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Вы можете отправлять SMS только из личного приложения \"Сообщения\"."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Использовать личный браузер"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Использовать рабочий браузер"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Позвонить"</string> @@ -2409,15 +2398,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Настроены раскладки клавиатуры для яз.: <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g> и др. Нажмите, чтобы изменить."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Физические клавиатуры настроены"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Нажмите, чтобы посмотреть подключенные клавиатуры."</string> - <string name="profile_label_private" msgid="6463418670715290696">"Личный"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Частный"</string> <string name="profile_label_clone" msgid="769106052210954285">"Клон"</string> <string name="profile_label_work" msgid="3495359133038584618">"Рабочий"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Рабочий 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Рабочий 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Тестовый"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Совместный"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Рабочий профиль"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Частное пространство"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клонированный"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Совместный"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Конфиденциальная информация в уведомлении скрыта"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Содержимое приложения исключено из демонстрации экрана в целях безопасности."</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Автоматически подключено к системам спутниковой связи"</string> @@ -2425,4 +2417,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Настройте разблокировку по отпечатку пальца заново"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Отпечаток пальца \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" оказался неудачным и был удален. Чтобы использовать разблокировку с помощью отпечатка пальца, настройте ее заново."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Отпечатки пальцев \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" и \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" оказались неудачными и были удалены. Чтобы использовать разблокировку с помощью отпечатка пальца, настройте ее заново."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Настройте фейсконтроль заново"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Модель лица оказалась неудачной и была удалена. Чтобы пользоваться фейсконтролем, настройте его заново."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Настроить"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Не сейчас"</string> </resources> diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml index 764b05178509..a8eda4f65c7e 100644 --- a/core/res/res/values-si/strings.xml +++ b/core/res/res/values-si/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: තත්පර <xliff:g id="TIME_DELAY">{2}</xliff:g> ට පසුව <xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"ජංගම ජාල ආරක්ෂක සැකසීම්"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"තව දැන ගන්න"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"තේරුණා"</string> <string name="fcComplete" msgid="1080909484660507044">"අංග කේතය සම්පූර්ණයි."</string> <string name="fcError" msgid="5325116502080221346">"සම්බන්ධතා ගැටළුවක් හෝ අවලංගු විශේෂාංග කේතයකි."</string> <string name="httpErrorOk" msgid="6206751415788256357">"හරි"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"හඬ සහායක"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"අගුලු දැමීම"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"නව දැනුම්දීම"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"භෞතික යතුරු පුවරුව"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ආරක්ෂාව"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"තිර රුව ගන්න"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"සංදර්ශකයේ තිර රුවක් ගැනීමට හැකිය."</string> <string name="dream_preview_title" msgid="5570751491996100804">"පෙරදසුන, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"ඉවත ලන්න"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"තත්ව තීරුව අබල කරන්න හෝ වෙනස් කරන්න"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"තත්ව තීරුව අක්රිය කිරීමට හෝ පද්ධති නිරූපක එකතු හෝ ඉවත් කිරීමට යෙදුමට අවසර දේ."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"තත්ත්ව තීරුව බවට පත්වීම"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"ඔබට දුරකථන ඇමතුම් ගත හැක්කේ ඔබේ පුද්ගලික Phone යෙදුමින් පමණයි. පුද්ගලික Phone මගින් කරන ලද ඇමතුම් ඔබේ පුද්ගලික ඇමතුම් ඉතිහාසයට එක් කරනු ලැබේ."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"ඔබට කෙටි පණුවඩ යැවිය හැක්කේ ඔබේ පුද්ගලික Messages යෙදුමින් පමණයි."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"පුද්ගලික බ්රව්සරය භාවිත කරන්න"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"කාර්යාල බ්රව්සරය භාවිත කරන්න"</string> <string name="miniresolver_call" msgid="6386870060423480765">"අමතන්න"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"කාර්යාලය 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"පරීක්ෂණය"</string> <string name="profile_label_communal" msgid="8743921499944800427">"වාර්ගික"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"කාර්යාල පැතිකඩ"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"රහසිගත අවකාශය"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"ක්ලෝන කරන්න"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"වාර්ගික"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"සංවේදී දැනුම්දීම් අන්තර්ගතය සැඟවී ඇත"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ආරක්ෂාව සඳහා යෙදුම් අන්තර්ගතය තිරය බෙදා ගැනීමෙන් සඟවා ඇත"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ඇඟිලි සලකුණු අගුලු හැරීම නැවත සකසන්න"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> හොඳින් ක්රියා නොකළේය, එය මකන ලදි ඇඟිලි සලකුණ මගින් ඔබේ දුරකථනය අගුලු හැරීමට එය නැවත සකසන්න."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> සහ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> හොඳින් ක්රියා නොකළේය, කාර්යසාධනය දියුණූ කිරීමට ඒවා මකන ලදි. ඔබේ ඇඟිලි සලකුණ මගින් ඔබේ දුරකථනය අගුලු හැරීමට ඒවා නැවත සකසන්න."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"මුහුණෙන් අගුලු හැරීම නැවත සකසන්න"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"ඔබේ මුහුණු මාදිලිය හොඳින් ක්රියා නොකරයි, එය මකන ලදි. මුහුණ මගින් ඔබේ දුරකථනය අගුලු හැරීමට එය නැවත සකසන්න."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"සකසන්න"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"දැන් නොවේ"</string> </resources> diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml index 35b176094265..46897cbbf7cb 100644 --- a/core/res/res/values-sk/strings.xml +++ b/core/res/res/values-sk/strings.xml @@ -155,31 +155,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po <xliff:g id="TIME_DELAY">{2}</xliff:g> s"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepresmerované"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nepresmerované"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Zabezpečenie mobilnej siete"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Šifrovanie, upozornenia na nešifrované siete"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Bol zaznamenaný prístup k identifikátoru zariadenia"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Sieť v okolí nahrala o <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> jedinečný identifikátor vášho zariadenia (IMSI alebo IMEI), keď ste používali SIM siete <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Sieť v okolí nahrala o <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> jedinečný identifikátor vášho zariadenia (IMSI alebo IMEI), keď ste používali SIM siete <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nZnamená to, že vaša poloha, aktivita alebo totožnosť bola zaznamenaná. Ide o bežný postup, ale môže to byť problém pre ľudí, ktorí majú obavy o ochranu súkromia."</string> + <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">"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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Dobre"</string> <string name="fcComplete" msgid="1080909484660507044">"Požiadavka zadaná pomocou kódu funkcie bola úspešne dokončená."</string> <string name="fcError" msgid="5325116502080221346">"Problém s pripojením alebo neplatný kód funkcie."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -297,6 +285,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Hlasový asistent"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Uzamknúť"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Nové upozornenie"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fyzická klávesnica"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Zabezpečenie"</string> @@ -373,8 +363,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Vytvoriť snímku obrazovky"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Je možné vytvoriť snímku obrazovky."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Ukážka, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"zavrieť"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"zakázanie alebo zmeny stavového riadka"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Umožňuje aplikácii vypnúť stavový riadok alebo pridať a odstrániť systémové ikony."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"vydávanie sa za stavový riadok"</string> @@ -2159,6 +2148,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Upozornenie s informáciami o rutinnom režime"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Bol zapnutý šetrič batérie"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Zníženie spotreby batérie predlžuje jej výdrž"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Šetrič batérie je zapnutý"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Šetrič batérie ja zapnutý, aby sa predĺžila výdrž batérie"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Šetrič batérie"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Šetrič batérie bol vypnutý."</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefón je dostatočne nabitý. Funkcie už nie sú obmedzené."</string> @@ -2233,10 +2224,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Chcete prepnúť na pracovnú aplikáciu?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Vaša organizácia vám povoľuje volať iba z pracovných aplikácií"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Vaša organizácia vám povoľuje posielať správy iba z pracovných aplikácií"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Volať môžete iba zo svojej osobnej telefónnej aplikácie. Hovory uskutočnené osobným telefónom budú pridané do vašej osobnej histórie hovorov."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Správy SMS môžete posielať iba zo svojho osobného komunikátora."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Použiť osobný prehliadač"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Použiť pracovný prehliadač"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Volať"</string> @@ -2416,6 +2405,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"3. pracovný"</string> <string name="profile_label_test" msgid="9168641926186071947">"Testovací"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Spoločný"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Pracovný profil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Súkromný priestor"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Spoločný"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Obsah citlivého upozornenia je skrytý"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Obsah aplikácie bol na účely zabezpečenia skrytý v zdieľaní obrazovky"</string> @@ -2424,4 +2417,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Znova nastavte odomknutie odtlačkom prsta"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Odtlačok <xliff:g id="FINGERPRINT">%s</xliff:g> nefungoval správne a bol odstránený. Ak chcete odomykať telefón odtlačkom prsta, nastavte ho znova."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Odtlačky <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> a <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nefungovali správne a boli odstránené. Ak chcete odomykať telefón odtlačkom prsta, nastavte ich znova."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Znova nastavte odomknutie tvárou"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Váš model tváre nefungoval správne a bol odstránený. Ak chcete odomykať telefón tvárou, nastavte ho znova."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Nastaviť"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Teraz nie"</string> </resources> diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml index f80b5aab99ea..2f10d584fed9 100644 --- a/core/res/res/values-sl/strings.xml +++ b/core/res/res/values-sl/strings.xml @@ -155,31 +155,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> po toliko sekundah: <xliff:g id="TIME_DELAY">{2}</xliff:g>"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ni posredovano"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: ni posredovano"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Varnost mobilnega omrežja"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Šifriranje, obvestila za nešifrirana omrežja"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Dostop do ID-ja naprave"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Ob <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> je omrežje v bližini zabeležilo enoznačni ID vaše naprave (IMSI ali IMEI) med uporabo kartice SIM omrežja <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Ob <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> je omrežje v bližini zabeležilo enoznačni ID vaše naprave (IMSI ali IMEI) med uporabo kartice SIM omrežja <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nTo pomeni, da je zabeležena vaša lokacija, dejavnost ali identiteta. To je običajna praksa, toda morda je to težava za ljudi, ki jih skrbi varnost."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Povezava s šifriranim omrežjem <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Povezava kartice SIM omrežja <xliff:g id="NETWORK_NAME">%1$s</xliff:g> je zdaj varnejša"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Povezava z nešifriranim omrežjem"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Klici, sporočila in podatki so trenutno ranljivejši pri uporabi kartice SIM omrežja <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Klici, sporočila in podatki so trenutno ranljivejši pri uporabi kartice SIM omrežja <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nKo bo povezava znova šifrirana, boste prejeli obvestilo."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Nastavitve varnosti mobilnega omrežja"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Več o tem"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Razumem"</string> <string name="fcComplete" msgid="1080909484660507044">"Koda funkcije je dokončana."</string> <string name="fcError" msgid="5325116502080221346">"Težava s povezavo ali neveljavna koda funkcije."</string> <string name="httpErrorOk" msgid="6206751415788256357">"V redu"</string> @@ -297,6 +285,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Glas. pomočnik"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Zakleni"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999 +"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Novo obvestilo"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fizična tipkovnica"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Varnost"</string> @@ -1745,7 +1735,7 @@ <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Ogledovanje in upravljanje zaslona"</string> <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Bere lahko vso vsebino na zaslonu ter prikaže vsebino prek drugih aplikacij."</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Ogledovanje in izvajanje dejanj"</string> - <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Spremlja lahko vaše interakcije z aplikacijo ali tipalom strojne opreme ter komunicira z aplikacijami v vašem imenu."</string> + <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Spremlja lahko vaše interakcije z aplikacijo ali tipalom ter komunicira z aplikacijami v vašem imenu."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Dovoli"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Zavrni"</string> <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Odmesti"</string> @@ -2158,6 +2148,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rutinsko informativno obvestilo o načinu delovanja"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Varčevanje z energijo baterije je vklopljeno"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Manjša poraba energije baterije za podaljšanje časa delovanja baterije"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Varčevanje z energijo baterije je vklopljeno"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Varčevanje z energijo baterije je vklopljeno za podaljšanje časa delovanja pri baterijskem napajanju"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Varčevanje z energijo baterije"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Varčevanje z energijo baterije je izklopljeno"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Baterija v telefonu je dovolj napolnjena. Funkcije niso več omejene."</string> @@ -2232,10 +2224,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Želite preklopiti na delovno aplikacijo?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Organizacija vam omogoča klicanje samo iz delovnih aplikacij."</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organizacija vam omogoča pošiljanje sporočil samo iz delovnih aplikacij."</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Klicati je mogoče samo v osebni aplikaciji Telefon. Klici, opravljeni z osebno aplikacijo Telefon, se dodajo v osebno zgodovino klicev."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Sporočila SMS je mogoče pošiljati samo v osebni aplikaciji Sporočila."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Uporabi osebni brskalnik"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Uporabi delovni brskalnik"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Pokliči"</string> @@ -2415,6 +2405,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Delo 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Preizkus"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Skupno"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Delovni profil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Zasebni prostor"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Skupno"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Občutljiva vsebina obvestila je bila skrita"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Pri deljenju zaslona je vsebina aplikacije skrita zaradi varnosti"</string> @@ -2423,4 +2417,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Vnovična nastavitev odklepanja s prstnim odtisom"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> ni deloval pravilno in je bil izbrisan. Znova ga nastavite, če želite telefon odklepati s prstnim odtisom."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> in <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> nista delovala pravilno in sta bila izbrisana. Znova ju nastavite, če želite telefon odklepati s prstnim odtisom."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Vnovična nastavitev odklepanja z obrazom"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Model obraza ni deloval pravilno in je bil izbrisan. Znova ga nastavite, če želite telefon odklepati z obrazom."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Nastavi"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Ne zdaj"</string> </resources> diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml index 9f9907a4b359..70980b61e972 100644 --- a/core/res/res/values-sq/strings.xml +++ b/core/res/res/values-sq/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pas <xliff:g id="TIME_DELAY">{2}</xliff:g> sekondash"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nuk u transferua"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Nuk u transferua"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Siguria e rrjetit celular"</string> + <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ë 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 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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"E kuptova"</string> <string name="fcComplete" msgid="1080909484660507044">"Kodi i funksionit është i plotë."</string> <string name="fcError" msgid="5325116502080221346">"Problem me lidhjen ose kod është i pavlefshëm."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Në rregull"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ndihma zanore"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Blloko"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Njoftim i ri"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tastiera fizike"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Siguria"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Nxirr një pamje të ekranit"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Mund të nxjerrë një pamje e ekranit."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Versioni paraprak, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"hiq"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"çaktivizo ose modifiko shiritin e statusit"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Lejon aplikacionin të çaktivizojë shiritin e statusit dhe të heqë ikonat e sistemit."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"të bëhet shiriti i statusit"</string> @@ -2157,6 +2146,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> + <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> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Të kalohet tek aplikacioni i punës?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Organizata jote të lejon që të telefonosh vetëm nga aplikacionet e punës"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organizata jote të lejon që të dërgosh mesazhe vetëm nga aplikacionet e punës"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Mund të bësh telefonata vetëm nga aplikacioni personal \"Telefoni\". Telefonatat e bëra me aplikacionin personal \"Telefoni\" do të shtohen te historiku personal i telefonatave."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Mund të dërgosh mesazhe SMS vetëm nga aplikacioni personal \"Mesazhet\"."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Përdor shfletuesin personal"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Përdor shfletuesin e punës"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Telefono"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Puna 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"I përbashkët"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profili i punës"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Hapësira private"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"I përbashkët"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Përmbajtjet delikate të njoftimeve janë fshehur"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Përmbajtja e aplikacionit është fshehur nga ndarja e ekranit për arsye sigurie"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"U lidh automatikisht me satelitin"</string> @@ -2423,4 +2415,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 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <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 c67819ae6d77..86387c95f727 100644 --- a/core/res/res/values-sr/strings.xml +++ b/core/res/res/values-sr/strings.xml @@ -154,31 +154,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> 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> + <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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Подешавања безбедности на мобилној мрежи"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Сазнајте више"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Важи"</string> <string name="fcComplete" msgid="1080909484660507044">"Кôд функције је извршен."</string> <string name="fcError" msgid="5325116502080221346">"Проблеми са везом или неважећи кôд функције."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Потврди"</string> @@ -296,6 +284,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Гласовна помоћ"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Закључавање"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Ново обавештење"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Физичка тастатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Безбедност"</string> @@ -339,7 +329,7 @@ <string name="permgroupdesc_storage" msgid="5378659041354582769">"приступ фајловима на уређају"</string> <string name="permgrouplab_readMediaAural" msgid="1858331312624942053">"Музика и звук"</string> <string name="permgroupdesc_readMediaAural" msgid="7565467343667089595">"приступ музици и аудио садржају на уређају"</string> - <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Слике и видео снимци"</string> + <string name="permgrouplab_readMediaVisual" msgid="4724874717811908660">"Слике и видеи"</string> <string name="permgroupdesc_readMediaVisual" msgid="4080463241903508688">"приступ сликама и видеима на уређају"</string> <string name="permgrouplab_microphone" msgid="2480597427667420076">"Микрофон"</string> <string name="permgroupdesc_microphone" msgid="1047786732792487722">"снима звук"</string> @@ -847,11 +837,11 @@ <string name="policylab_watchLogin" msgid="7599669460083719504">"Надзор покушаја откључавања екрана"</string> <string name="policydesc_watchLogin" product="tablet" msgid="2388436408621909298">"Прати број нетачно унетих лозинки приликом откључавања екрана и закључава таблет или брише податке са таблета ако је нетачна лозинка унета превише пута."</string> <string name="policydesc_watchLogin" product="tv" msgid="2140588224468517507">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава Android TV уређај или брише све податке са Android TV уређаја ако се унесе превише нетачних лозинки."</string> - <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Прати број нетачно унетих лозинки при откључавању екрана и закључава систем за инфо-забаву или брише све податке са система за инфо-забаву ако је нетачна лозинка унета превише пута."</string> + <string name="policydesc_watchLogin" product="automotive" msgid="7011438994051251521">"Прати број нетачно унетих лозинки при откључавању екрана и закључава систем за информације и забаву или брише све податке са система за информације и забаву ако је нетачна лозинка унета превише пута."</string> <string name="policydesc_watchLogin" product="default" msgid="4885030206253600299">"Прати број нетачно унетих лозинки при откључавању екрана и закључава телефон или брише све податке са телефона ако је нетачна лозинка унета превише пута."</string> <string name="policydesc_watchLogin_secondaryUser" product="tablet" msgid="2049038943004297474">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава таблет или брише све податке овог корисника ако се унесе превише нетачних лозинки."</string> <string name="policydesc_watchLogin_secondaryUser" product="tv" msgid="8965224107449407052">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава Android TV уређај или брише све податке овог корисника ако се унесе превише нетачних лозинки."</string> - <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава систем за инфо-забаву или брише све податке овог профила ако се унесе превише нетачних лозинки."</string> + <string name="policydesc_watchLogin_secondaryUser" product="automotive" msgid="7180857406058327941">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава систем за информације и забаву или брише све податке овог профила ако се унесе превише нетачних лозинки."</string> <string name="policydesc_watchLogin_secondaryUser" product="default" msgid="9177645136475155924">"Надгледа број нетачних лозинки унетих при откључавању екрана и закључава телефон или брише све податке овог корисника ако се унесе превише нетачних лозинки."</string> <string name="policylab_resetPassword" msgid="214556238645096520">"Промена закључавања екрана"</string> <string name="policydesc_resetPassword" msgid="4626419138439341851">"Мења откључавање екрана."</string> @@ -860,13 +850,13 @@ <string name="policylab_wipeData" msgid="1359485247727537311">"Брисање свих података"</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="automotive" msgid="660804547737323300">"Брише податке на систему за информације и забаву без упозорења ресетовањем на фабричка подешавања."</string> <string name="policydesc_wipeData" product="default" msgid="8036084184768379022">"Брисање података на телефону без упозорења ресетовањем на фабричка подешавања."</string> <string name="policylab_wipeData_secondaryUser" product="automotive" msgid="115034358520328373">"Брисање података профила"</string> <string name="policylab_wipeData_secondaryUser" product="default" msgid="413813645323433166">"Обриши податке корисника"</string> <string name="policydesc_wipeData_secondaryUser" product="tablet" msgid="2336676480090926470">"Брише податке овог корисника на овом таблету без упозорења."</string> <string name="policydesc_wipeData_secondaryUser" product="tv" msgid="2293713284515865200">"Брише податке овог корисника на овом Android TV уређају без упозорења."</string> - <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Брише податке овог профила на овом систему за инфо-забаву без упозорења."</string> + <string name="policydesc_wipeData_secondaryUser" product="automotive" msgid="4658832487305780879">"Брише податке овог профила на овом систему за информације и забаву без упозорења."</string> <string name="policydesc_wipeData_secondaryUser" product="default" msgid="2788325512167208654">"Брише податке овог корисника на овом телефону без упозорења."</string> <string name="policylab_setGlobalProxy" msgid="215332221188670221">"Подесите глобални прокси сервер уређаја"</string> <string name="policydesc_setGlobalProxy" msgid="7149665222705519604">"Подешава глобални прокси уређаја који ће се користити док су смернице омогућене. Само власник уређаја може да подеси глобални прокси."</string> @@ -2157,6 +2147,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> + <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> @@ -2231,10 +2223,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Телефонске позиве можете да упућујете само из личне апликације Телефон. Позиви упућени помоћу личне апликације Телефон додају се у личну историју позива."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"SMS поруке можете да шаљете само из личне апликације Messages."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Користи лични прегледач"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Користи пословни прегледач"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Позови"</string> @@ -2414,8 +2404,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Посао 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Тест"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Заједничко"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Пословни профил"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Приватан простор"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Клонирано"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Заједничко"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Осетљив садржај обавештења је скривен"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Садржај апликације је скривен за дељење садржаја екрана због безбедности"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Аутоматски повезано са сателитом"</string> @@ -2423,4 +2416,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Поново подесите откључавање отиском прста"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> није функционисао и избрисали смо га. Поново га подесите да бисте телефон откључавали отиском прста."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> и <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> нису функционисали и избрисали смо их. Поново их подесите да бисте телефон откључавали отиском прста."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Поново подесите откључавање лицем"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Ваш модел лица није функционисао и избрисали смо га. Поново га подесите да бисте телефон откључавали лицем."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Подеси"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Не сада"</string> </resources> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index 01b30d2672eb..6c15ad8ff76f 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g><xliff:g id="DIALING_NUMBER">{1}</xliff:g> efter <xliff:g id="TIME_DELAY">{2}</xliff:g> sekunder"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>. Vidarebefordras inte"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Vidarebefordras inte"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobilnätverkssäkerhet"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Kryptering, aviseringar för okrypterade nätverk"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Enhets-id har kommits åt"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Kl. <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> loggade ett nätverk i närheten din enhets unika id (IMSI- eller IMEI-nummer) medan det använde ditt SIM-kort från <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Kl. <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> loggade ett nätverk i närheten din enhets unika id (IMSI- eller IMEI-nummer) medan det använde ditt SIM-kort från <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nDetta innebär att din plats, aktivitet eller identitet har loggats. Detta är allmän praxis, men kan vara ett problem för personer som är måna om sin integritet."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Ansluten till det krypterade nätverket <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Anlutningen för SIM-kortet från <xliff:g id="NETWORK_NAME">%1$s</xliff:g> är säkrare nu"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Ansluten till okrypterat nätverk"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Samtal, meddelanden och data är i nuläget mer sårbara när du använder SIM-kortet från <xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Samtal, meddelanden och data är i nuläget mer sårbara när du använder SIM-kortet från <xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nNär din anslutning krypteras igen får du en till avisering."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Inställningar för mobilnätverkssäkerhet"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Läs mer"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Funktionskoden är fullständig."</string> <string name="fcError" msgid="5325116502080221346">"Anslutningsproblem eller ogiltig funktionskod."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Låsning"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Ny avisering"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fysiskt tangentbord"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Säkerhet"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ta skärmbild"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Kan ta en skärmbild av skärmen."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Förhandsgranskar <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"stäng"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"inaktivera eller ändra statusfält"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Tillåter att appen inaktiverar statusfältet eller lägger till och tar bort systemikoner."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"visas i statusfältet"</string> @@ -636,12 +625,12 @@ <string name="permdesc_postNotification" msgid="5974977162462877075">"Tillåter att appen visar aviseringar"</string> <string name="permlab_turnScreenOn" msgid="219344053664171492">"Slå på skärmen"</string> <string name="permdesc_turnScreenOn" msgid="4394606875897601559">"Tillåter att appen slår på skärmen."</string> - <string name="permlab_useBiometric" msgid="6314741124749633786">"använd biometrisk maskinvara"</string> - <string name="permdesc_useBiometric" msgid="7502858732677143410">"Tillåter att appen använder biometrisk maskinvara vid autentisering"</string> - <string name="permlab_manageFingerprint" msgid="7432667156322821178">"hantera maskinvara för fingeravtryck"</string> + <string name="permlab_useBiometric" msgid="6314741124749633786">"använd biometrisk hårdvara"</string> + <string name="permdesc_useBiometric" msgid="7502858732677143410">"Tillåter att appen använder biometrisk hårdvara vid autentisering"</string> + <string name="permlab_manageFingerprint" msgid="7432667156322821178">"hantera hårdvara för fingeravtryck"</string> <string name="permdesc_manageFingerprint" msgid="2025616816437339865">"Tillåter att appen anropar metoder för att lägga till och radera fingeravtrycksmallar."</string> - <string name="permlab_useFingerprint" msgid="1001421069766751922">"använda maskinvara för fingeravtryck"</string> - <string name="permdesc_useFingerprint" msgid="412463055059323742">"Tillåter att appen använder maskinvara för fingeravtryck vid autentisering"</string> + <string name="permlab_useFingerprint" msgid="1001421069766751922">"använda hårdvara för fingeravtryck"</string> + <string name="permdesc_useFingerprint" msgid="412463055059323742">"Tillåter att appen använder hårdvara för fingeravtryck vid autentisering"</string> <string name="permlab_audioWrite" msgid="8501705294265669405">"göra ändringar i din musiksamling"</string> <string name="permdesc_audioWrite" msgid="8057399517013412431">"Tillåter att appen gör ändringar i din musiksamling."</string> <string name="permlab_videoWrite" msgid="5940738769586451318">"göra ändringar i din videosamling"</string> @@ -655,7 +644,7 @@ <string name="biometric_dialog_default_title" msgid="55026799173208210">"Verifiera din identitet"</string> <string name="biometric_dialog_default_subtitle" msgid="8457232339298571992">"Fortsätt med hjälp av din biometriska data"</string> <string name="biometric_or_screen_lock_dialog_default_subtitle" msgid="159539678371552009">"Fortsätt med hjälp av din biometriska data eller skärmlåset"</string> - <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk maskinvara är inte tillgänglig"</string> + <string name="biometric_error_hw_unavailable" msgid="2494077380540615216">"Biometrisk hårdvara är inte tillgänglig"</string> <string name="biometric_error_user_canceled" msgid="6732303949695293730">"Autentiseringen avbröts"</string> <string name="biometric_not_recognized" msgid="5106687642694635888">"Identifierades inte"</string> <string name="biometric_face_not_recognized" msgid="5535599455744525200">"Ansiktet känns inte igen"</string> @@ -683,7 +672,7 @@ <string name="fingerprint_authenticated" msgid="2024862866860283100">"Fingeravtrycket har autentiserats"</string> <string name="face_authenticated_no_confirmation_required" msgid="8867889115112348167">"Ansiktet har autentiserats"</string> <string name="face_authenticated_confirmation_required" msgid="6872632732508013755">"Ansiktet har autentiserats. Tryck på Bekräfta"</string> - <string name="fingerprint_error_hw_not_available" msgid="7755729484334001137">"Det finns ingen maskinvara för fingeravtryck"</string> + <string name="fingerprint_error_hw_not_available" msgid="7755729484334001137">"Det finns ingen hårdvara för fingeravtryck"</string> <string name="fingerprint_error_no_space" msgid="7285481581905967580">"Det gick inte att konfigurera fingeravtryck"</string> <string name="fingerprint_error_timeout" msgid="7361192266621252164">"Tiden för fingeravtrycksinställning gick ut. Försök igen."</string> <string name="fingerprint_error_canceled" msgid="5541771463159727513">"Fingeravtrycksåtgärden avbröts"</string> @@ -745,7 +734,7 @@ <string name="face_acquired_mouth_covering_detected_alt" msgid="1122294982850589766">"Något täcker ansiktet. Hela ansiktet måste synas."</string> <string-array name="face_acquired_vendor"> </string-array> - <string name="face_error_hw_not_available" msgid="5085202213036026288">"Ansiktsverifiering går ej. Otillgänglig maskinvara."</string> + <string name="face_error_hw_not_available" msgid="5085202213036026288">"Ansiktsverifiering går ej. Otillgänglig hårdvara."</string> <string name="face_error_timeout" msgid="2598544068593889762">"Försök att använda ansiktslåset igen"</string> <string name="face_error_no_space" msgid="5649264057026021723">"Kan inte lagra ny ansiktsdata. Radera först gammal data."</string> <string name="face_error_canceled" msgid="2164434737103802131">"Ansiktsåtgärden har avbrutits."</string> @@ -1744,7 +1733,7 @@ <string name="accessibility_service_screen_control_title" msgid="190017412626919776">"Visa och styra skärmen"</string> <string name="accessibility_service_screen_control_description" msgid="6946315917771791525">"Den kan läsa allt innehåll på skärmen och visa innehåll över andra appar."</string> <string name="accessibility_service_action_perform_title" msgid="779670378951658160">"Visa och vidta åtgärder"</string> - <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan registrera din användning av en app eller maskinvarusensor och interagera med appar åt dig."</string> + <string name="accessibility_service_action_perform_description" msgid="2718852014003170558">"Den kan registrera din användning av en app eller hårdvarusensor och interagera med appar åt dig."</string> <string name="accessibility_dialog_button_allow" msgid="2092558122987144530">"Tillåt"</string> <string name="accessibility_dialog_button_deny" msgid="4129575637812472671">"Neka"</string> <string name="accessibility_dialog_button_uninstall" msgid="2952465517671708108">"Avinstallera"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Avisering om rutinläge"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Batterisparläget har aktiverats"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Minskar batteriförbrukning för att förlänga batteritiden"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Batterisparläget är aktiverat"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batterisparläget är aktiverat för att förlänga batteritiden"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Batterisparläge"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Batterisparläget har inaktiverats"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefonen har laddats tillräckligt. Funktioner begränsas inte längre."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Vill du byta till jobbappen?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Organisationen tillåter endast att du ringer samtal med jobbappar"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Organisationen tillåter endast att du skickar meddelanden med jobbappar"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Du kan bara ringa telefonsamtal från din personliga app Telefon. Samtal som görs med din personliga Telefon läggs till i din personliga samtalshistorik."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Du kan bara skicka sms från din personliga Messages-app."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Använd privat webbläsare"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Använd jobbwebbläsare"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Ring"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Arbete 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Allmän"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Jobbprofil"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Privat område"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klona"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Allmän"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Känsligt aviseringsinnehåll dolt"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Av säkerhetsskäl döljs appinnehållet vid skärmdelning"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Automatiskt ansluten till satellit"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Konfigurera fingeravtryckslås igen"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> fungerade inte bra och har raderats. Konfigurera det igen för att låsa upp telefonen med fingeravtryck."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> och <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> fungerade inte bra och har raderats. Konfigurera dem igen för att låsa upp telefonen med fingeravtryck."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Konfigurera ansiktslås igen"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Ansiktsmodellen fungerade inte bra och har raderats. Konfigurera den igen för att låsa upp telefonen med ansiktet."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Ställ in"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Inte nu"</string> </resources> diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml index df308001e4de..1437a3830d44 100644 --- a/core/res/res/values-sw/strings.xml +++ b/core/res/res/values-sw/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> baada ya sekunde <xliff:g id="TIME_DELAY">{2}</xliff:g>"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Haijasambazwa"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Haijatumiwa mwingine"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Usalama wa mtandao wa simu"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Usimbaji fiche, arifa za mitandao ambayo haijasimbwa kwa njia fiche"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Kitambulisho cha kifaa kimefikiwa"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Saa <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, mtandao wa karibu nawe ulirekodi kitambulisho cha kipekee cha kifaa chako (IMSI au IMEI) ulipokuwa ukitumia SIM yako ya <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Saa <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, mtandao wa karibu nawe ulirekodi kitambulisho cha kipekee cha kifaa chako (IMSI au IMEI) ulipokuwa ukitumia SIM yako ya <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nHii inamaanisha kuwa data ya mahali ulipo, shughuli au utambulisho wako imewekwa kwenye kumbukumbu. Hii ni desturi ya kawaida, lakini inaweza kuwa tatizo kwa watu wanaozingatia masuala ya faragha kwa undani."</string> + <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 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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Nimeelewa"</string> <string name="fcComplete" msgid="1080909484660507044">"Msimbo wa kipengele umekamilika."</string> <string name="fcError" msgid="5325116502080221346">"Tatizo la muunganisho au msimbo batili wa kipengele."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Sawa"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Usaidizi wa Sauti"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Funga"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Arifa mpya"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Kibodi halisi"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Usalama"</string> @@ -2156,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Arifa ya maelezo ya Hali ya Kawaida"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Kiokoa Betri kimewashwa"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Inapunguza matumizi ya betri ili kuongeza muda wa matumizi ya betri"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Kiokoa Betri kimewashwa"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Kiokoa Betri kimewashwa ili kuongeza muda wa matumizi ya betri"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Kiokoa betri"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Kiokoa Betri kimezimwa"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Simu ina chaji ya kutosha. Vipengele havizuiliwi tena."</string> @@ -2230,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Ungependa kutumia programu ya kazini?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Shirika lako linakuruhusu upige simu ukitumia programu za kazini pekee"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Shirika lako linakuruhusu utume ujumbe ukitumia programu za kazini pekee"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Unaweza kupiga simu pekee kwa kutumia programu yako binafsi ya Simu. Simu unazopiga kwa kutumia programu binafsi ya Simu zitawekwa kwenye rekodi yako binafsi ya simu zilizopigwa."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Unaweza kutuma ujumbe wa SMS pekee kwa kutumia programu yako binafsi ya Messages."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Tumia kivinjari cha binafsi"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Tumia kivinjari cha kazini"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Piga simu"</string> @@ -2413,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Wa 3 wa Kazini"</string> <string name="profile_label_test" msgid="9168641926186071947">"Jaribio"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Unaoshirikiwa"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Wasifu wa kazini"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Sehemu ya faragha"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Nakala"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Wasifu wa pamoja"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Maudhui nyeti kwenye arifa yamefichwa"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Maudhui ya programu yamefichwa ili yasionekane kwenye skrini ya pamoja kwa sababu za kiusalama"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Imeunganishwa kiotomatiki na satelaiti"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Weka tena mipangilio ya Kufungua kwa Alama ya Kidole"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Alama ya <xliff:g id="FINGERPRINT">%s</xliff:g> ilikuwa na hitilafu na imefutwa. Iweke tena ili ufungue simu yako kwa alama ya kidole."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Alama za <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> na <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> zilikuwa na hitilafu na zimefutwa. Ziweke tena ili ufungue simu yako kwa alama ya kidole."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Weka tena mipangilio ya Kufungua kwa Uso"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Muundo wako wa uso ulikuwa na hitilafu na umefutwa. Uweke tena ili ufungue simu yako kwa uso."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Weka mipangilio"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Si sasa"</string> </resources> diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml index 69b3f9bbba17..231b14c6a94d 100644 --- a/core/res/res/values-ta/strings.xml +++ b/core/res/res/values-ta/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> வினாடிகளுக்குப் பிறகு <xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> சிம்மைப் பயன்படுத்தும்போது அருகிலுள்ள நெட்வொர்க் உங்கள் சாதனத்தின் தனிப்பட்ட ஐடியை (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> சிம்மைப் பயன்படுத்தும்போது அருகிலுள்ள நெட்வொர்க் உங்கள் சாதனத்தின் தனிப்பட்ட ஐடியை (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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"மொபைல் நெட்வொர்க் பாதுகாப்பு அமைப்புகள்"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"மேலும் அறிக"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"சரி"</string> <string name="fcComplete" msgid="1080909484660507044">"பிரத்தியேக குறியீடு முடிந்தது."</string> <string name="fcError" msgid="5325116502080221346">"இணைப்பு சிக்கல் அல்லது தவறான அம்சக் குறியீடு."</string> <string name="httpErrorOk" msgid="6206751415788256357">"சரி"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"குரல் உதவி"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"பூட்டு"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"புதிய அறிவிப்பு"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"கைமுறை கீபோர்டு"</string> <string name="notification_channel_security" msgid="8516754650348238057">"பாதுகாப்பு"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"ஸ்கிரீன்ஷாட்டை எடுக்கும்"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"டிஸ்ப்ளேவை ஸ்கிரீன்ஷாட் எடுக்க முடியும்."</string> <string name="dream_preview_title" msgid="5570751491996100804">"மாதிரிக்காட்சி, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"மூடுக"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"நிலைப் பட்டியை முடக்குதல் அல்லது மாற்றுதல்"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"நிலைப் பட்டியை முடக்க அல்லது முறைமையில் ஐகான்களைச் சேர்க்க மற்றும் அகற்ற ஆப்ஸை அனுமதிக்கிறது."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"நிலைப் பட்டியில் இருக்கும்"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <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> <string name="miniresolver_call" msgid="6386870060423480765">"அழை"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"பணி 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"பரிசோதனை"</string> <string name="profile_label_communal" msgid="8743921499944800427">"பொது"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"பணிக் கணக்கு"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ரகசிய இடம்"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"குளோன்"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"பொது"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"உணர்வுபூர்வமான அறிவிப்பு உள்ளடக்கம் மறைக்கப்பட்டது"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"பாதுகாப்பிற்காக, திரைப் பகிர்வில் இருந்து ஆப்ஸ் உள்ளடக்கம் மறைக்கப்பட்டுள்ளது"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"சாட்டிலைட்டுடன் தானாக இணைக்கப்பட்டது"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"கைரேகை அன்லாக் அம்சத்தை மீண்டும் அமையுங்கள்"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> சரியாகச் செயல்படவில்லை என்பதால் அது நீக்கபட்டது. கைரேகை மூலம் உங்கள் மொபைலை அன்லாக் செய்ய அதை மீண்டும் அமையுங்கள்."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> மற்றும் <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> சரியாகச் செயல்படவில்லை என்பதால் அவை நீக்கப்பட்டன. கைரேகை மூலம் உங்கள் மொபைலை அன்லாக் செய்ய அவற்றை மீண்டும் அமையுங்கள்."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"\'முகம் காட்டித் திறத்தல்\' அம்சத்தை மீண்டும் அமையுங்கள்"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"உங்கள் முகத் தோற்றப் பதிவு சரியாகச் செயல்படவில்லை என்பதால் அது நீக்கப்பட்டது. உங்கள் முகத்தைப் பயன்படுத்தி மொபைலை அன்லாக் செய்ய அதை மீண்டும் அமையுங்கள்."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"அமை"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"இப்போது வேண்டாம்"</string> </resources> diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml index b6c6086a8040..cf4e19408909 100644 --- a/core/res/res/values-te/strings.xml +++ b/core/res/res/values-te/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> సెకన్ల తర్వాత <xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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> + <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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"మరింత తెలుసుకోండి"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"అర్థమైంది"</string> <string name="fcComplete" msgid="1080909484660507044">"లక్షణం కోడ్ పూర్తయింది."</string> <string name="fcError" msgid="5325116502080221346">"కనెక్షన్ సమస్య లేదా లక్షణం కోడ్ చెల్లదు."</string> <string name="httpErrorOk" msgid="6206751415788256357">"సరే"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"వాయిస్ అసిస్టెంట్"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"లాక్ చేయండి"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"కొత్త నోటిఫికేషన్"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"భౌతిక కీబోర్డ్"</string> <string name="notification_channel_security" msgid="8516754650348238057">"సెక్యూరిటీ"</string> @@ -1223,7 +1213,7 @@ <string name="rating_label" msgid="1837085249662154601">"{rating,plural, =1{{max}కి ఒక స్టార్}other{{max}కి # స్టార్లు}}"</string> <string name="in_progress" msgid="2149208189184319441">"ప్రోగ్రెస్లో ఉంది"</string> <string name="whichApplication" msgid="5432266899591255759">"దీన్ని ఉపయోగించి చర్యను పూర్తి చేయండి"</string> - <string name="whichApplicationNamed" msgid="6969946041713975681">"%1$sను ఉపయోగించి చర్యను పూర్తి చేయి"</string> + <string name="whichApplicationNamed" msgid="6969946041713975681">"%1$sను ఉపయోగించి చర్యను పూర్తి చేయండి"</string> <string name="whichApplicationLabel" msgid="7852182961472531728">"చర్యను పూర్తి చేయి"</string> <string name="whichViewApplication" msgid="5733194231473132945">"దీనితో తెరువు"</string> <string name="whichViewApplicationNamed" msgid="415164730629690105">"%1$sతో తెరువు"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <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> <string name="miniresolver_call" msgid="6386870060423480765">"కాల్ చేయండి"</string> @@ -2413,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"ఆఫీస్ 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"పరీక్ష"</string> <string name="profile_label_communal" msgid="8743921499944800427">"కమ్యూనల్"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"వర్క్ ప్రొఫైల్"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"ప్రైవేట్ స్పేస్"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"క్లోన్"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"కమ్యూనల్"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"గోప్యమైన నోటిఫికేషన్ కంటెంట్ దాచబడింది"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"సెక్యూరిటీ కోసం స్క్రీన్ షేర్ నుండి యాప్ కంటెంట్ దాచబడింది"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"వేలిముద్ర అన్లాక్ను మళ్లీ సెటప్ చేయండి"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> సరిగ్గా పని చేయడం లేదు, తొలగించబడింది. వేలిముద్రతో మీ ఫోన్ను అన్లాక్ చేయడానికి దాన్ని మళ్లీ సెటప్ చేయండి."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>, <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> బాగా పని చేయడం లేదు, తొలగించబడ్డాయి. మీ వేలిముద్రతో మీ ఫోన్ను అన్లాక్ చేయడానికి వాటిని మళ్లీ సెటప్ చేయండి."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ఫేస్ అన్లాక్ను మళ్లీ సెటప్ చేయండి"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"మీ ఫేస్ మోడల్ సరిగ్గా పని చేయడం లేదు, తొలగించబడింది. ఫేస్తో మీ ఫోన్ను అన్లాక్ చేయడానికి దాన్ని మళ్లీ సెటప్ చేయండి."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"సెటప్ చేయండి"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ఇప్పుడు కాదు"</string> </resources> diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml index c9a202742476..59809c92ed96 100644 --- a/core/res/res/values-th/strings.xml +++ b/core/res/res/values-th/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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="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="scNullCipherIssueActionSettings" msgid="5888857706424639946">"การตั้งค่าการรักษาความปลอดภัยของเครือข่ายมือถือ"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"ดูข้อมูลเพิ่มเติม"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"รับทราบ"</string> <string name="fcComplete" msgid="1080909484660507044">"รหัสฟีเจอร์เสร็จสมบูรณ์"</string> <string name="fcError" msgid="5325116502080221346">"พบปัญหาในการเชื่อมต่อหรือรหัสฟีเจอร์ไม่ถูกต้อง"</string> <string name="httpErrorOk" msgid="6206751415788256357">"ตกลง"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"ตัวช่วยเสียง"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"ปิดล็อก"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"การแจ้งเตือนใหม่"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"แป้นพิมพ์จริง"</string> <string name="notification_channel_security" msgid="8516754650348238057">"ความปลอดภัย"</string> @@ -1631,7 +1621,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> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"คุณจะโทรออกได้จากแอปโทรศัพท์ส่วนตัวเท่านั้น โดยระบบจะเพิ่มการโทรที่ดำเนินการด้วยแอปดังกล่าวลงในประวัติการโทรส่วนตัวของคุณ"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"คุณจะส่งข้อความ SMS ได้จากแอปข้อความส่วนตัวเท่านั้น"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ใช้เบราว์เซอร์ส่วนตัว"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"ใช้เบราว์เซอร์งาน"</string> <string name="miniresolver_call" msgid="6386870060423480765">"โทร"</string> @@ -2413,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"งาน 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"ทดสอบ"</string> <string name="profile_label_communal" msgid="8743921499944800427">"ส่วนกลาง"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"โปรไฟล์งาน"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"พื้นที่ส่วนตัว"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"โคลน"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"ส่วนกลาง"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"เนื้อหาการแจ้งเตือนที่ละเอียดอ่อนซ่อนอยู่"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"ซ่อนเนื้อหาแอปจากการแชร์หน้าจอเพื่อความปลอดภัย"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"เชื่อมต่อกับดาวเทียมโดยอัตโนมัติ"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"ตั้งค่าการปลดล็อกด้วยลายนิ้วมืออีกครั้ง"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g>ทำงานได้ไม่ดีและถูกลบออกไปแล้ว ตั้งค่าอีกครั้งเพื่อปลดล็อกโทรศัพท์ด้วยลายนิ้วมือ"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> และ <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ทำงานได้ไม่ดีและถูกลบออกไปแล้ว ตั้งค่าอีกครั้งเพื่อปลดล็อกโทรศัพท์ด้วยลายนิ้วมือ"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"ตั้งค่าการปลดล็อกด้วยใบหน้าอีกครั้ง"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"รูปแบบใบหน้าของคุณทำงานได้ไม่ดีและถูกลบออกไปแล้ว ตั้งค่าอีกครั้งเพื่อปลดล็อกโทรศัพท์ด้วยใบหน้า"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"ตั้งค่า"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ไว้ทีหลัง"</string> </resources> diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml index 4428dc8fcd54..7ba81ad9b55e 100644 --- a/core/res/res/values-tl/strings.xml +++ b/core/res/res/values-tl/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> pagkatapos ng <xliff:g id="TIME_DELAY">{2}</xliff:g> (na) segundo"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Hindi naipasa"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Hindi ipinasa"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Seguridad ng mobile network"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Pag-encrypt, mga notification para sa mga hindi naka-encrypt na network"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Na-access ang Device ID"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Nang <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, na-record ng isang kalapit na network ang natatanging ID ng iyong device (IMSI o IMEI) habang ginagamit ang iyong <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Nang <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, na-record ng isang kalapit na network ang natatanging ID ng iyong device (IMSI o IMEI) habang ginagamit ang iyong <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM.\n\nIbig sabihin, na-log ang iyong lokasyon, aktibidad, o pagkakakilanlan. Isa itong karaniwang kagawian pero posibleng isang isyu para sa mga taong nag-aalala tungkol sa privacy."</string> + <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 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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Kumpleto na ang code ng tampok."</string> <string name="fcError" msgid="5325116502080221346">"Problema sa koneksyon o di-wastong code ng tampok."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"I-lockdown"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Bagong notification"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Pisikal na keyboard"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Seguridad"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Kumuha ng screenshot"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Puwedeng kumuha ng screenshot ng display."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Preview, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"i-dismiss"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"i-disable o baguhin ang status bar"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Pinapayagan ang app na i-disable ang status bar o magdagdag at mag-alis ng mga icon ng system."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"maging status bar"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Notification ng impormasyon ng Routine Mode"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Na-on ang Pantipid ng Baterya"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Binabawasan ang paggamit sa baterya para mapatagal ang baterya"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Naka-on ang Pantipid ng Baterya"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Naka-on ang Pantipid ng Baterya para mapahaba ang tagal ng baterya"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Pantipid ng Baterya"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Na-off ang Pantipid ng Baterya"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"May sapat na charge ang telepono. Hindi na pinaghihigpitan ang mga feature."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Lumipat sa app para sa trabaho?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Pinapayagan ka lang ng iyong organisasyon na tumawag mula sa mga app para sa trabaho"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Pinapayagan ka lang ng iyong organisasyon na magpadala ng mga mensahe mula sa mga app para sa trabaho"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Puwede ka lang tumawag sa telepono mula sa iyong personal na App na Telepono. Idaragdag sa iyong personal na history ng tawag ang mga tawag na ginawa gamit ang personal na Telepono mo."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Puwede ka lang magpadala ng mga mensaheng SMS mula sa iyong personal na Messages app."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Gamitin ang personal na browser"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Gamitin ang browser sa trabaho"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Tumawag"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Trabaho 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Communal"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Profile sa trabaho"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Pribadong space"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Communal"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Nakatago ang content ng sensitibong notification"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Nakatago ang content ng app mula sa pagbabahagi ng screen para sa seguridad"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Awtomatikong nakakonekta sa satellite"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"I-set up ulit ang Pag-unlock Gamit ang Fingerprint"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Hindi gumagana nang maayos ang <xliff:g id="FINGERPRINT">%s</xliff:g> at na-delete na ito. I-set up ulit ito para ma-unlock ang iyong telepono sa pamamagitan ng fingerprint."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Hindi gumagana nang maayos ang <xliff:g id="FINGERPRINT_0">%1$s</xliff:g> at <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> at na-delete na ang mga ito. I-set up ulit ang mga ito para ma-unlock ang iyong telepono gamit ang fingerprint mo."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"I-set up ulit ang Pag-unlock Gamit ang Mukha"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Hindi gumagana nang maayos ang iyong face model at na-delete na ito. I-set up ulit ito para ma-unlock ang iyong telepono sa pamamagitan ng mukha."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"I-set up"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Huwag muna"</string> </resources> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index 3d022a82bffd..64498b524b24 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="TIME_DELAY">{2}</xliff:g> saniye sonra <xliff:g id="DIALING_NUMBER">{1}</xliff:g>"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönlendirilmedi"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yönlendirilmedi"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobil ağ güvenliği"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Şifreleme, şifrelenmemiş ağlarla ilgili bildirimler"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Cihaz kimliğine erişildi"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Yakındaki bir ağ, <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> sularında <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM\'inizi kullanırken cihazınızın benzersiz kimliğini (IMSI veya IMEI) kaydetti"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Yakındaki bir ağ, <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> sularında <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g> SIM\'inizi kullanırken cihazınızın benzersiz kimliğini (IMSI veya IMEI) kaydetti.\n\nDolayısıyla konumunuz, etkinliğiniz veya kimliğiniz günlüğe kaydedildi. Bu yaygın bir uygulama olsa da gizlilik konusunda endişe duyan kişiler açısından sorun olabilir."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Şifrelenmiş <xliff:g id="NETWORK_NAME">%1$s</xliff:g> ağına bağlanıldı"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM bağlantısı artık daha güvenli"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Şifrelenmemiş ağa bağlanıldı"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Aramalar, mesajlar ve veriler şu anda <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM\'inizi kullanırken daha savunmasız durumda"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Aramalar, mesajlar ve veriler şu anda <xliff:g id="NETWORK_NAME">%1$s</xliff:g> SIM\'inizi kullanırken daha savunmasız durumda.\n\nBağlantınız tekrar şifrelendiğinde başka bir bildirim alacaksınız."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobil ağ güvenliği ayarları"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Daha fazla bilgi"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Anladım"</string> <string name="fcComplete" msgid="1080909484660507044">"Özellik kodu tamamlandı."</string> <string name="fcError" msgid="5325116502080221346">"Bağlantı sorunu veya geçersiz özellik kodu."</string> <string name="httpErrorOk" msgid="6206751415788256357">"Tamam"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Sesli Yardım"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Tam kilitleme"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Yeni bildirim"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Fiziksel klavye"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Güvenlik"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Ekran görüntüsü al"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Ekran görüntüsü alınabilir."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Önizleme, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"kapat"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"durum çubuğunu devre dışı bırak veya değiştir"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Uygulamaya, durum çubuğunu devre dışı bırakma ve sistem simgelerini ekleyip kaldırma izni verir."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"durum çubuğunda olma"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Rutin Modu bilgi bildirimi"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Pil Tasarrufu açıldı"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Pil ömrünü uzatmak için pil kullanımını azaltma"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Pil Tasarrufu açık"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Pil ömrünü uzatmak için Pil Tasarrufu özelliği açıldı"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Pil Tasarrufu"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Pil Tasarrufu kapatıldı"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon yeterince şarj oldu. Özellikler artık kısıtlanmış değil."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"İş uygulamasına geçilsin mi?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Kuruluşunuz yalnızca iş uygulamalarından telefon etmenize izin veriyor"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Kuruluşunuz yalnızca iş uygulamalarından mesaj göndermenize izin veriyor"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Yalnızca kişisel Telefon uygulamanızdan telefon edebilirsiniz. Kişisel Telefon uygulamasıyla yapılan aramalar, kişisel çağrı geçmişinize eklenir."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Yalnızca kişisel Mesajlar uygulamanızdan SMS gönderebilirsiniz."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Kişisel tarayıcıyı kullan"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"İş tarayıcısını kullan"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Telefon et"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"İş 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Paylaşılan"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"İş profili"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Özel alan"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Klon"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Paylaşılan"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Hassas bildirim içerikleri gizlendi"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Uygulama içerikleri, güvenlik nedeniyle ekran paylaşımında gizlendi"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Uyduya otomatik olarak bağlandı"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Parmak İzi Kilidi\'ni tekrar kurun"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> iyi çalışmadığı için silindi. Telefonunuzun kilidini parmak iziyle açmak için tekrar kurun."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> ve <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> iyi çalışmadığı için silindi. Telefonunuzun kilidini parmak izinizle açmak için tekrar kurun."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Yüz Tanıma Kilidi\'ni tekrar kurun"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Yüz modeliniz iyi çalışmadığı için silindi. Telefonunuzun kilidini yüzünüzle açmak için tekrar kurun."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Ayarla"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Şimdi değil"</string> </resources> diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml index e272f5f0e85f..fcea7bb43e01 100644 --- a/core/res/res/values-uk/strings.xml +++ b/core/res/res/values-uk/strings.xml @@ -155,31 +155,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Безпека мобільної мережі"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Шифрування, сповіщення для незашифрованих мереж"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Отримано доступ до ідентифікатора пристрою"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"О <xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> у мережі поблизу зафіксовано унікальний ідентифікатор вашого пристрою (IMSI або IMEI) під час використання вашої SIM-карти <xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <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="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> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Сервісний код виконано."</string> <string name="fcError" msgid="5325116502080221346">"Пробл. підключення чи недійсний ідентифікатор."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -297,6 +285,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Голос. підказки"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Блокування"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Нове сповіщення"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Фізична клавіатура"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Безпека"</string> @@ -373,8 +363,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Робити знімки екрана"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Може робити знімки екрана."</string> <string name="dream_preview_title" msgid="5570751491996100804">"<xliff:g id="DREAM_NAME">%1$s</xliff:g> (попередній перегляд)"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"закрити"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"вимикати чи змін. рядок стану"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Дозволяє програмі вимикати рядок стану чи додавати та видаляти піктограми системи."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"відображатися як рядок стану"</string> @@ -2159,6 +2148,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> + <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> @@ -2233,10 +2224,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Ви можете телефонувати лише з особистого додатка Телефон. Такі дзвінки реєструватимуться у вашій особистій історії викликів."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Ви можете надсилати SMS лише з особистого додатка Повідомлення."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Використати особистий веб-переглядач"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Використати робочий веб-переглядач"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Телефонувати"</string> @@ -2416,8 +2405,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Робочий профіль 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Тестовий профіль"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Спільний профіль"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Робочий профіль"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Приватний простір"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Копія профілю"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Спільний профіль"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Чутливий вміст сповіщення приховано"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"З міркувань безпеки вміст додатка приховано під час показу екрана"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Автоматично підключено до супутника"</string> @@ -2425,4 +2417,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Налаштуйте розблокування відбитком пальця повторно"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"Відбиток \"<xliff:g id="FINGERPRINT">%s</xliff:g>\" працював неналежним чином, і його видалено. Налаштуйте його ще раз, щоб розблоковувати телефон за допомогою відбитка пальця."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"Відбитки \"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>\" і \"<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>\" працювали неналежним чином, і їх видалено. Налаштуйте їх ще раз, щоб розблоковувати телефон за допомогою відбитка пальця."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Налаштуйте фейс-контроль повторно"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Модель обличчя працювала неналежним чином, і її видалено. Налаштуйте її ще раз, щоб розблоковувати телефон за допомогою фейс-контролю."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Налаштувати"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Не зараз"</string> </resources> diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml index a2b7327f5c56..fc23d99d03d8 100644 --- a/core/res/res/values-ur/strings.xml +++ b/core/res/res/values-ur/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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> میں، قریبی نیٹ ورک نے آپ کے <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="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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"مزید جانیں"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"سمجھ آ گئی"</string> <string name="fcComplete" msgid="1080909484660507044">"خصوصیت کوڈ مکمل۔"</string> <string name="fcError" msgid="5325116502080221346">"کنکشن مسئلہ یا غلط خصوصیت کوڈ۔"</string> <string name="httpErrorOk" msgid="6206751415788256357">"ٹھیک ہے"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Voice Assist"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"مقفل"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"نئی اطلاع"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"فزیکل کی بورڈ"</string> <string name="notification_channel_security" msgid="8516754650348238057">"سیکیورٹی"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"اسکرین شاٹ لیں"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"ڈسپلے کا اسکرین شاٹ لیا جا سکتا ہے۔"</string> <string name="dream_preview_title" msgid="5570751491996100804">"پیش منظر، <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"برخاست کریں"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"اسٹیٹس بار کو غیر فعال یا اس میں ترمیم کریں"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"ایپ کو اسٹیٹس بار غیر فعال کرنے یا سسٹم آئیکنز شامل کرنے اور ہٹانے کی اجازت دیتا ہے۔"</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"بطور اسٹیٹس بار کام لیں"</string> @@ -657,7 +646,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> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"آپ صرف اپنی ذاتی فون ایپ سے فون کالز کر سکتے ہیں۔ ذاتی فون سے کی گئی کالز آپ کی ذاتی کال کی سرگزشت میں شامل کر دی جائیں گی۔"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"آپ صرف اپنی ذاتی پیغامات ایپ سے SMS پیغامات بھیج سکتے ہیں۔"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"ذاتی براؤزر استعمال کریں"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"ورک براؤزر استعمال کریں"</string> <string name="miniresolver_call" msgid="6386870060423480765">"کال کریں"</string> @@ -2414,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"تیسری دفتری پروفائل"</string> <string name="profile_label_test" msgid="9168641926186071947">"ٹیسٹ"</string> <string name="profile_label_communal" msgid="8743921499944800427">"کمیونل"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"دفتری پروفائل"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"پرائیویٹ اسپیس"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"کلون"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"کمیونل"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"حساس اطلاعی مواد چھپا ہوا ہے"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"سیکیورٹی کے مد نظر ایپ کا مواد اسکرین کے اشتراک سے چھپا ہوا ہے"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"فنگر پرنٹ اَن لاک کو دوبارہ سیٹ اپ کریں"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> اچھی طرح کام نہیں کر رہا تھا اور حذف کر دیا گیا تھا۔ اپنے فون کو فنگر پرنٹ سے غیر مقفل کرنے کے لیے، اسے دوبارہ سیٹ اپ کریں۔"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> اور <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> اچھی طرح کام نہیں کر رہے تھے اور انہیں حذف کر دیا گیا تھا۔ اپنے فون کو اپنے فنگر پرنٹ سے غیر مقفل کرنے کے لیے انہیں دوبارہ سیٹ اپ کریں۔"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"فیس اَن لاک کو دوبارہ سیٹ اپ کریں"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"آپ کے چہرے کا ماڈل اچھی طرح کام نہیں کر رہا تھا اور حذف کر دیا گیا تھا۔ اپنے فون کو چہرے سے غیر مقفل کرنے کے لیے، اسے دوبارہ سیٹ اپ کریں۔"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"سیٹ اپ کریں"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"ابھی نہیں"</string> </resources> diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml index b35c33b4dddb..0ef4ddc4cc0b 100644 --- a/core/res/res/values-uz/strings.xml +++ b/core/res/res/values-uz/strings.xml @@ -153,31 +153,19 @@ <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> soniyadan so‘ng"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Yo‘naltirilmadi"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: yo‘naltirilmadi"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Mobil tarmoq xavfsizligi"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Shifrlash, shifrlanmagan tarmoqlar haqidagi bildirishnomalar"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"Qurilma identifikatoriga ruxsat olingan"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> da SIM karta (<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>) ishlatilganda atrofdagi tarmoqda qurilmaning unikal identifikatori (IMSI yoki IMEI) qayd qilingan"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g> da SIM karta (<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>) ishlatilganda atrofdagi tarmoqda qurilmaning unikal identifikatori (IMSI yoki IMEI) qayd qilingan.\n\nJoylashuvingiz, faolligingiz yoki kimligingiz qayd qilingan. Bu odatiy holat boʻlsa ham, maxfiyligi haqida xavotirlanuvchi odamlar uchun muammo boʻlishi mumkin."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Shifrlangan tarmoqqa (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) ulandi"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Endi SIM karta (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) aloqasi yanada xavfsiz"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Shifrlanmagan tarmoqqa ulandi"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Hozir SIM karta (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) orqali chaqiruvlar, xabarlar va maʼlumotlar himoyasi zaif"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Hozir SIM karta (<xliff:g id="NETWORK_NAME">%1$s</xliff:g>) orqali chaqiruvlar, xabarlar va maʼlumotlar himoyasi zaif.\n\nAloqa qayta shifrlanganda boshqa bildirishnoma olasiz."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Mobil tarmoq xavfsizligi sozlamalari"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Batafsil"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"OK"</string> <string name="fcComplete" msgid="1080909484660507044">"Maxsus kod bajarildi."</string> <string name="fcError" msgid="5325116502080221346">"Tarmoqda xato yoki maxsus kod noto‘g‘ri."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Ovozli yordam"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Qulflash"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Yangi bildirishnoma"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Tashqi klaviatura"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Xavfsizlik"</string> @@ -2156,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Kun tartibi rejimi haqidagi bildirishnoma"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Quvvat tejalishi yoqildi"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Batareya tejalganda batareya quvvati uzoqroq vaqtga yetadi"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Quvvat tejash rejimi yoniq"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Batareya quvvatini uzaytirish uchun Quvvat tejash yoqilgan"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Quvvat tejash"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Quvvat tejash rejimi faolsizlantirildi"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Telefon yetarli quvvatlandi. Funksiyalar endi cheklovlarsiz ishlaydi."</string> @@ -2230,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Ishga oid ilovaga almashtirilsinmi?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Tashkilotingiz faqat ishga oid ilovalar orqali chaqiruvga ruxsat beradi"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Tashkilotingiz faqat ishga oid ilovalar orqali xabarlar yuborishga ruxsat beradi"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Siz faqat shaxsiy Telefon ilovasi orqali telefon qila olasiz. Shaxsiy Telefon orqali amalga oshgan chaqiruvlar shaxsiy chaqiruvlar tarixida saqlanadi."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Siz faqat shaxsiy Xabarlar ilovasi orqali SMS yubora olasiz."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Shaxsiy brauzerdan foydalanish"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Ishga oid brauzerdan foydalanish"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Chaqiruv"</string> @@ -2406,15 +2396,18 @@ <string name="keyboard_layout_notification_more_than_three_selected_message" msgid="1581834181578206937">"Klaviatura terilmasi bunga sozlandi: <xliff:g id="LAYOUT_1">%1$s</xliff:g>, <xliff:g id="LAYOUT_2">%2$s</xliff:g>, <xliff:g id="LAYOUT_3">%3$s</xliff:g>… Oʻzgartirish uchun ustiga bosing."</string> <string name="keyboard_layout_notification_multiple_selected_title" msgid="5242444914367024499">"Tashqi klaviaturalar sozlandi"</string> <string name="keyboard_layout_notification_multiple_selected_message" msgid="6576533454124419202">"Klaviaturalarni ochish uchun ustiga bosing"</string> - <string name="profile_label_private" msgid="6463418670715290696">"Yopiq"</string> + <string name="profile_label_private" msgid="6463418670715290696">"Maxfiy"</string> <string name="profile_label_clone" msgid="769106052210954285">"Nusxasini yaratish"</string> <string name="profile_label_work" msgid="3495359133038584618">"Ish"</string> <string name="profile_label_work_2" msgid="4691533661598632135">"Ish 2"</string> <string name="profile_label_work_3" msgid="4834572253956798917">"Ish 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Test"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Umumiy"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Ish profili"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Maxfiy makon"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Nusxalash"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Umumiy"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Bildirishnomadagi maxfiy axborot berkitildi"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Ekran namoyishida xavfsizlik maqsadida ilova kontenti berkitildi"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Sputnikka avtomatik ulandi"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Barmoq izi bilan ochish funksiyasini qayta sozlang"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> yaxshi ishlamadi va oʻchirib tashlandi. Telefonni barmoq izi bilan ochish uchun uni qayta sozlang."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> va <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> yaxshi ishlamadi va oʻchirib tashlandi. Telefonni barmoq izi bilan ochish uchun ularni qayta sozlang."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Yuz bilan ochishni qayta sozlash"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Yuzingiz mobeli yaxshi ishlamadi va oʻchirib tashlandi. Telefonni yuz bilan ochish uchun uni qayta sozlang."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Sozlash"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Hozir emas"</string> </resources> diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml index e7c94d622459..6ff6528878e3 100644 --- a/core/res/res/values-vi/strings.xml +++ b/core/res/res/values-vi/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> sau <xliff:g id="TIME_DELAY">{2}</xliff:g> giây"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Không được chuyển tiếp"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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ị đã 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 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">"Cài đặt an ninh mạng di động"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Tìm hiểu thêm"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <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> <string name="fcError" msgid="5325116502080221346">"Sự cố kết nối hoặc mã tính năng không hợp lệ."</string> <string name="httpErrorOk" msgid="6206751415788256357">"OK"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Trợ lý thoại"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Khóa"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Thông báo mới"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Bàn phím vật lý"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Bảo mật"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"Chụp ảnh màn hình"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"Có thể chụp ảnh màn hình."</string> <string name="dream_preview_title" msgid="5570751491996100804">"Bản xem trước, <xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"đóng"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"vô hiệu hóa hoặc sửa đổi thanh trạng thái"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"Cho phép ứng dụng vô hiệu hóa thanh trạng thái hoặc thêm và xóa biểu tượng hệ thống."</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"trở thành thanh trạng thái"</string> @@ -2157,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Thông báo cung cấp thông tin về chế độ sạc thông thường"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Đã bật Trình tiết kiệm pin"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Giảm mức sử dụng pin để kéo dài thời lượng pin"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Trình tiết kiệm pin đang bật"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Trình tiết kiệm pin được bật để kéo dài thời lượng pin"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Trình tiết kiệm pin"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Trình tiết kiệm pin đã tắt"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Điện thoại còn đủ pin. Các tính năng không bị hạn chế nữa."</string> @@ -2231,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Chuyển sang ứng dụng công việc?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Tổ chức của bạn chỉ cho phép bạn gọi điện bằng ứng dụng công việc"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Tổ chức của bạn chỉ cho phép bạn gửi tin nhắn bằng ứng dụng công việc"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Bạn chỉ có thể gọi điện qua ứng dụng Điện thoại cá nhân của mình. Cuộc gọi được thực hiện bằng ứng dụng Điện thoại cá nhân sẽ được thêm vào nhật ký cuộc gọi cá nhân của bạn."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Bạn chỉ có thể gửi tin nhắn SMS qua ứng dụng Tin nhắn cá nhân của mình."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Dùng trình duyệt cá nhân"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Dùng trình duyệt công việc"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Gọi"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Công việc 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Kiểm thử"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Dùng chung"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Hồ sơ công việc"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Không gian riêng tư"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Nhân bản"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Dùng chung"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"Đã ẩn nội dung thông báo nhạy cảm"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Nội dung ứng dụng bị ẩn khỏi tính năng chia sẻ màn hình vì lý do bảo mật"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"Đã tự động kết nối với vệ tinh"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Thiết lập lại tính năng Mở khoá bằng vân tay"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"<xliff:g id="FINGERPRINT">%s</xliff:g> không dùng được và đã bị xoá. Hãy thiết lập lại để mở khoá điện thoại bằng vân tay."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> và <xliff:g id="FINGERPRINT_1">%2$s</xliff:g> không dùng được và đã bị xoá. Hãy thiết lập lại để mở khoá điện thoại bằng vân tay."</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Thiết lập lại tính năng Mở khoá bằng khuôn mặt"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Mẫu khuôn mặt của bạn không dùng được và đã bị xoá. Hãy thiết lập lại để mở khoá điện thoại bằng khuôn mặt."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Thiết lập"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Để sau"</string> </resources> diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml index 31acd9af164c..52662149b23a 100644 --- a/core/res/res/values-watch/config.xml +++ b/core/res/res/values-watch/config.xml @@ -93,4 +93,7 @@ <!-- If this is true, allow wake from theater mode from motion. --> <bool name="config_allowTheaterModeWakeFromMotion">true</bool> + + <!-- True if the device supports system decorations on secondary displays. --> + <bool name="config_supportsSystemDecorsOnSecondaryDisplays">false</bool> </resources> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 10ceb5b9188c..10243220d600 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g>秒后<xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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>,当您使用<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="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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"了解详情"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"知道了"</string> <string name="fcComplete" msgid="1080909484660507044">"功能代码已拨完。"</string> <string name="fcError" msgid="5325116502080221346">"出现连接问题或功能代码无效。"</string> <string name="httpErrorOk" msgid="6206751415788256357">"确定"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"语音助理"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"锁定"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"实体键盘"</string> <string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string> @@ -2156,6 +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> + <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> @@ -2230,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"您只能使用个人“电话”应用打电话。用个人“电话”进行的通话将会添加到您的个人通话记录中。"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"您只能使用个人“信息”应用发送短信。"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"使用个人浏览器"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"使用工作浏览器"</string> <string name="miniresolver_call" msgid="6386870060423480765">"拨打电话"</string> @@ -2413,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"工作 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"测试"</string> <string name="profile_label_communal" msgid="8743921499944800427">"共用"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"工作资料"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"私密空间"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"克隆"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"共用"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"已隐藏敏感通知内容"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"为安全起见而在屏幕共享画面中处于隐藏状态的应用内容"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"自动连接到卫星"</string> @@ -2422,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"重新设置指纹解锁功能"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"“<xliff:g id="FINGERPRINT">%s</xliff:g>”无法正常使用,系统已将其删除。如要通过指纹解锁功能来解锁手机,请重新设置。"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"“<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>”和“<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>”无法正常使用,系统已将它们删除。如要通过指纹解锁功能来解锁手机,请重新设置。"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"重新设置“人脸解锁”功能"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"您的脸部模型无法正常使用,系统已将其删除。如要通过人脸解锁功能来解锁手机,请重新设置。"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"设置"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"以后再说"</string> </resources> diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml index 541ea63f51f4..b1157f7df6d5 100644 --- a/core/res/res/values-zh-rHK/strings.xml +++ b/core/res/res/values-zh-rHK/strings.xml @@ -153,31 +153,19 @@ <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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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>,附近網絡使用你的 「<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="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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"瞭解詳情"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"知道了"</string> <string name="fcComplete" msgid="1080909484660507044">"功能碼輸入完成。"</string> <string name="fcError" msgid="5325116502080221346">"連線問題或功能碼無效。"</string> <string name="httpErrorOk" msgid="6206751415788256357">"確定"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"語音助手"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"鎖定"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"實體鍵盤"</string> <string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"擷取螢幕擷圖"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"可以擷取螢幕截圖。"</string> <string name="dream_preview_title" msgid="5570751491996100804">"預覽,<xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"關閉"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"停用或修改狀態列"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"允許應用程式停用狀態列,並可新增或移除系統圖示。"</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"成為狀態列"</string> @@ -664,11 +653,11 @@ <string name="biometric_error_generic" msgid="6784371929985434439">"驗證時發生錯誤"</string> <string name="screen_lock_app_setting_name" msgid="6054944352976789228">"使用螢幕鎖定"</string> <string name="screen_lock_dialog_default_subtitle" msgid="120359538048533695">"如要繼續操作,請輸入螢幕鎖定解鎖憑證"</string> - <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"請用力按住感應器"</string> + <string name="fingerprint_acquired_partial" msgid="4323789264604479684">"請按住感應器"</string> <string name="fingerprint_acquired_insufficient" msgid="2410176550915730974">"無法辨識指紋,請再試一次。"</string> <string name="fingerprint_acquired_imager_dirty" msgid="1770676120848224250">"請清潔指紋感應器,然後再試一次"</string> <string name="fingerprint_acquired_imager_dirty_alt" msgid="9169582140486372897">"請清潔感應器,然後再試一次"</string> - <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"請用力按住感應器"</string> + <string name="fingerprint_acquired_too_fast" msgid="1628459767349116104">"請按住感應器"</string> <string name="fingerprint_acquired_too_slow" msgid="6683510291554497580">"手指移動太慢,請重試。"</string> <string name="fingerprint_acquired_already_enrolled" msgid="2285166003936206785">"改用其他指紋"</string> <string name="fingerprint_acquired_too_bright" msgid="3863560181670915607">"太亮"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"你只可使用個人「電話」應用程式撥打電話。透過個人「電話」應用程式撥出的電話會在個人通話記錄中顯示。"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"使用個人「訊息」應用程式時,只能傳送短訊。"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"使用個人瀏覽器"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"使用工作瀏覽器"</string> <string name="miniresolver_call" msgid="6386870060423480765">"打電話"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"工作 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"測試"</string> <string name="profile_label_communal" msgid="8743921499944800427">"共用"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"工作設定檔"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"私人空間"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"複製"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"共用"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"已隱藏敏感通知內容"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"為安全起見,應用程式內容已從分享螢幕畫面隱藏"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"已自動連線至衛星"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"重新設定「指紋解鎖」功能"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"由於「<xliff:g id="FINGERPRINT">%s</xliff:g>」無法正常運作,因此系統已將其刪除。請重新設定,才能使用指紋解鎖手機。"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"由於「<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>」和「<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>」無法正常運作,因此系統已將其刪除。請重新設定,才能使用指紋解鎖手機。"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"重新設定「面孔解鎖」功能"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"由於面部模型無法正常運作,因此系統已將其刪除。請重新設定,才能使用面孔解鎖手機。"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"設定"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"暫時不要"</string> </resources> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 8720a9d0ca6e..da43a12f8fe1 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>:<xliff:g id="TIME_DELAY">{2}</xliff:g> 秒後 <xliff:g id="DIALING_NUMBER">{1}</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> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <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>,鄰近網路使用你的「<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="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> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"瞭解詳情"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"我知道了"</string> <string name="fcComplete" msgid="1080909484660507044">"功能碼輸入完成。"</string> <string name="fcError" msgid="5325116502080221346">"連線發生問題或功能碼無效。"</string> <string name="httpErrorOk" msgid="6206751415788256357">"確定"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"語音小幫手"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"鎖定"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"超過 999"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"新通知"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"實體鍵盤"</string> <string name="notification_channel_security" msgid="8516754650348238057">"安全性"</string> @@ -371,8 +361,7 @@ <string name="capability_title_canTakeScreenshot" msgid="3895812893130071930">"擷取螢幕畫面"</string> <string name="capability_desc_canTakeScreenshot" msgid="7762297374317934052">"可以擷取螢幕畫面。"</string> <string name="dream_preview_title" msgid="5570751491996100804">"預覽,<xliff:g id="DREAM_NAME">%1$s</xliff:g>"</string> - <!-- no translation found for dream_accessibility_action_click (7392398629967797805) --> - <skip /> + <string name="dream_accessibility_action_click" msgid="7392398629967797805">"關閉"</string> <string name="permlab_statusBar" msgid="8798267849526214017">"停用或變更狀態列"</string> <string name="permdesc_statusBar" msgid="5809162768651019642">"允許應用程式停用狀態列,並可新增或移除系統圖示。"</string> <string name="permlab_statusBarService" msgid="2523421018081437981">"以狀態列顯示"</string> @@ -2157,6 +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> + <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> @@ -2231,10 +2222,8 @@ <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> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"只能使用個人「電話」應用程式撥打電話。透過個人「電話」應用程式撥出的電話會顯示在個人通話記錄中。"</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"使用個人的訊息應用程式時,只能傳送簡訊。"</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"使用個人瀏覽器"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"使用工作瀏覽器"</string> <string name="miniresolver_call" msgid="6386870060423480765">"撥號"</string> @@ -2414,8 +2403,11 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"工作 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"測試"</string> <string name="profile_label_communal" msgid="8743921499944800427">"通用"</string> - <!-- no translation found for redacted_notification_message (1520587845842228816) --> - <skip /> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"工作資料夾"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"私人空間"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"複製"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"共通"</string> + <string name="redacted_notification_message" msgid="1520587845842228816">"系統已隱藏含有私密資訊的通知內容"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"為安全起見,分享螢幕畫面未顯示應用程式內容"</string> <string name="satellite_notification_title" msgid="4026338973463121526">"已自動連上衛星"</string> @@ -2423,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"重新設定指紋解鎖"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"「<xliff:g id="FINGERPRINT">%s</xliff:g>」無法正常運作,因此系統已將其刪除。請重新設定,才能用指紋解鎖手機。"</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"「<xliff:g id="FINGERPRINT_0">%1$s</xliff:g>」和「<xliff:g id="FINGERPRINT_1">%2$s</xliff:g>」無法正常運作,因此系統已將其刪除。請重新設定,才能用指紋解鎖手機。"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"重新設定人臉解鎖"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"臉部模型無法正常運作,因此系統已將其刪除。請重新設定,才能用臉解鎖手機。"</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"設定"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"暫時不要"</string> </resources> diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml index 7f14f04d93c1..7749b1b6f04c 100644 --- a/core/res/res/values-zu/strings.xml +++ b/core/res/res/values-zu/strings.xml @@ -153,31 +153,19 @@ <string name="cfTemplateForwardedTime" msgid="735042369233323609">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: <xliff:g id="DIALING_NUMBER">{1}</xliff:g> emuva kwamasekhondi angu-<xliff:g id="TIME_DELAY">{2}</xliff:g>"</string> <string name="cfTemplateRegistered" msgid="5619930473441550596">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: Akudlulisiwe"</string> <string name="cfTemplateRegisteredTime" msgid="5222794399642525045">"<xliff:g id="BEARER_SERVICE_CODE">{0}</xliff:g>: akudlulisiwe"</string> - <!-- no translation found for scCellularNetworkSecurityTitle (7752521808690294384) --> - <skip /> - <!-- no translation found for scCellularNetworkSecuritySummary (7042036754550545005) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueTitle (2898888825129970328) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummaryNotification (3699930821270580416) --> - <skip /> - <!-- no translation found for scIdentifierDisclosureIssueSummary (7283387338827749276) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedTitle (234717016411824969) --> - <skip /> - <!-- no translation found for scNullCipherIssueEncryptedSummary (8577510708842150475) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedTitle (3978071464929453915) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummaryNotification (7386936934128110388) --> - <skip /> - <!-- no translation found for scNullCipherIssueNonEncryptedSummary (5093428974513703253) --> - <skip /> - <!-- no translation found for scNullCipherIssueActionSettings (5888857706424639946) --> - <skip /> + <string name="scCellularNetworkSecurityTitle" msgid="7752521808690294384">"Ezokuphepha kwenethiwekhi yeselula"</string> + <string name="scCellularNetworkSecuritySummary" msgid="7042036754550545005">"Ukubethela, izaziso zamanethiwekhi angabetheliwe"</string> + <string name="scIdentifierDisclosureIssueTitle" msgid="2898888825129970328">"I-ID yedivayisi ifinyelelwe"</string> + <string name="scIdentifierDisclosureIssueSummaryNotification" msgid="3699930821270580416">"Ngo-<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, inethiwekhi eseduze irekhode i-ID ehlukile yedivayisi yakho (i-IMSI noma i-IMEI) ngenkathi usebenzisa i-SIM yakho ye-<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>"</string> + <string name="scIdentifierDisclosureIssueSummary" msgid="7283387338827749276">"Ngo-<xliff:g id="DISCLOSURE_TIME">%1$s</xliff:g>, inethiwekhi eseduze irekhode i-ID ehlukile yedivayisi yakho (i-IMSI noma i-IMEI) ngenkathi usebenzisa i-SIM yakho ye-<xliff:g id="DISCLOSURE_NETWORK">%2$s</xliff:g>.\n\nLokhu kusho ukuthi indawo yakho, umsebenzi, noma ubuwena bufakiwe. Lokhu kuwumkhuba ovamile kodwa kungase kube inkinga kubantu abathintekayo mayelana nobumfihlo."</string> + <string name="scNullCipherIssueEncryptedTitle" msgid="234717016411824969">"Ixhumeke kunethiwekhi ebethelwe ye-<xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueEncryptedSummary" msgid="8577510708842150475">"Uxhumo lwe-SIM ye-<xliff:g id="NETWORK_NAME">%1$s</xliff:g> luvikeleke kakhulu manje"</string> + <string name="scNullCipherIssueNonEncryptedTitle" msgid="3978071464929453915">"Ixhumeke kunethiwekhi engabetheliwe"</string> + <string name="scNullCipherIssueNonEncryptedSummaryNotification" msgid="7386936934128110388">"Amakholi, imilayezo, nedatha okwamanje zisengozini kakhulu ngenkathi usebenzisa i-SIM yakho ye-<xliff:g id="NETWORK_NAME">%1$s</xliff:g>"</string> + <string name="scNullCipherIssueNonEncryptedSummary" msgid="5093428974513703253">"Amakholi, imilayezo, nedatha okwamanje zisengozini kakhulu ngenkathi usebenzisa i-SIM yakho ye-<xliff:g id="NETWORK_NAME">%1$s</xliff:g>.\n\nUma uxhumano lwakho lubethelwa futhi, uzothola esinye isaziso."</string> + <string name="scNullCipherIssueActionSettings" msgid="5888857706424639946">"Amasethingi ezokuphepha kwenethiwekhi yeselula"</string> <string name="scNullCipherIssueActionLearnMore" msgid="7896642417214757769">"Funda kabanzi"</string> - <!-- no translation found for scNullCipherIssueActionGotIt (8747796640866585787) --> - <skip /> + <string name="scNullCipherIssueActionGotIt" msgid="8747796640866585787">"Ngiyezwa"</string> <string name="fcComplete" msgid="1080909484660507044">"Ikhodi yesici iqedile."</string> <string name="fcError" msgid="5325116502080221346">"Inkinga yoxhumano noma ikhodi yesici engalungile."</string> <string name="httpErrorOk" msgid="6206751415788256357">"KULUNGILE"</string> @@ -295,6 +283,8 @@ <string name="global_action_voice_assist" msgid="6655788068555086695">"Isisekeli sezwi"</string> <string name="global_action_lockdown" msgid="2475471405907902963">"Khiya"</string> <string name="status_bar_notification_info_overflow" msgid="3330152558746563475">"999+"</string> + <!-- no translation found for notification_compact_heads_up_reply (2425293958371284340) --> + <skip /> <string name="notification_hidden_text" msgid="2835519769868187223">"Isaziso esisha"</string> <string name="notification_channel_physical_keyboard" msgid="5417306456125988096">"Ikhibhodi ephathekayo"</string> <string name="notification_channel_security" msgid="8516754650348238057">"Ukuphepha"</string> @@ -2156,6 +2146,8 @@ <string name="dynamic_mode_notification_channel_name" msgid="2986926422100223328">"Isaziso solwazi lwe-Routine Mode"</string> <string name="dynamic_mode_notification_title" msgid="1388718452788985481">"Isilondolozi Sebhethri sivuliwe"</string> <string name="dynamic_mode_notification_summary" msgid="1639031262484979689">"Ukwehlisa ukusetshenziswa kwebhethri ukuze kunwetshiswe impilo yebhethri"</string> + <string name="dynamic_mode_notification_title_v2" msgid="5072385242078021152">"Isilondolozi sebhethri sivuliwe"</string> + <string name="dynamic_mode_notification_summary_v2" msgid="2142444344663147938">"Isilondolozi Sebhethri sivuliwe ukuze kunwetshwe impilo yebhethri"</string> <string name="battery_saver_notification_channel_name" msgid="3918243458067916913">"Isilondolozi sebhethri"</string> <string name="battery_saver_off_notification_title" msgid="7637255960468032515">"Isilondolozi sebhethri sivaliwe"</string> <string name="battery_saver_charged_notification_summary" product="default" msgid="5544457317418624367">"Ifoni inokushajwa okwanele. Izici azisakhawulelwe."</string> @@ -2230,10 +2222,8 @@ <string name="miniresolver_switch_to_work" msgid="1042640606122638596">"Shintshela ku-app yasemsebenzini?"</string> <string name="miniresolver_call_information" msgid="6739417525304184083">"Inhlangano yakho ikuvumela kuphela ukuthi wenze amakholi ngama-app asemsebenzini"</string> <string name="miniresolver_sms_information" msgid="4311292661329483088">"Inhlangano yakho ikuvumela ukuthumela imilayezo kusuka kuma-app omsebenzi kuphela"</string> - <!-- no translation found for miniresolver_private_space_phone_information (4469511223312488570) --> - <skip /> - <!-- no translation found for miniresolver_private_space_messages_information (111285656327622118) --> - <skip /> + <string name="miniresolver_private_space_phone_information" msgid="4469511223312488570">"Ungenza amakholi wefoni kuphela nge-app yakho Yefoni yomuntu siqu. Amakholi enziwe Ngefoni yomuntu siqu azongezwa kumlando wakho wekholi womuntu siqu."</string> + <string name="miniresolver_private_space_messages_information" msgid="111285656327622118">"Ungathumela imiyalezo ye-SMS ukusuka ku-app yomuntu siqu ye-Messages kuphela."</string> <string name="miniresolver_use_personal_browser" msgid="776072682871133308">"Sebenzisa isiphequluli somuntu siqu"</string> <string name="miniresolver_use_work_browser" msgid="543575306251952994">"Sebenzisa isiphequluli somsebenzi"</string> <string name="miniresolver_call" msgid="6386870060423480765">"Fona"</string> @@ -2413,6 +2403,10 @@ <string name="profile_label_work_3" msgid="4834572253956798917">"Umsebenzi 3"</string> <string name="profile_label_test" msgid="9168641926186071947">"Hlola"</string> <string name="profile_label_communal" msgid="8743921499944800427">"Okomphakathi"</string> + <string name="accessibility_label_managed_profile" msgid="3366526886209832641">"Iphrofayela yomsebenzi"</string> + <string name="accessibility_label_private_profile" msgid="1436459319135548969">"Indawo engasese"</string> + <string name="accessibility_label_clone_profile" msgid="7579118375042398784">"Yenza i-Clone"</string> + <string name="accessibility_label_communal_profile" msgid="1437173163111334791">"Okomphakathi"</string> <string name="redacted_notification_message" msgid="1520587845842228816">"Okuqukethwe kwesaziso esizwelayo kufihliwe"</string> <string name="redacted_notification_action_title" msgid="6942924973335920935"></string> <string name="screen_not_shared_sensitive_content" msgid="7058419185079565001">"Okuqukethwe kwe-app kufihliwe kusuka ekwabelaneni kwesikrini ngokuvikelwa"</string> @@ -2421,4 +2415,15 @@ <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> + <string name="fingerprint_dangling_notification_title" msgid="7362075195588639989">"Setha Ukuvula ngesigxivizo somunwe futhi"</string> + <!-- no translation found for fingerprint_dangling_notification_msg_1 (8517140433796229725) --> + <skip /> + <!-- no translation found for fingerprint_dangling_notification_msg_2 (7578829498452127613) --> + <skip /> + <string name="fingerprint_dangling_notification_msg_all_deleted_1" msgid="2927018569542316055">"I-<xliff:g id="FINGERPRINT">%s</xliff:g> ibingasebenzi kahle futhi isuliwe. Phinde uyisethe ukuze uvule ifoni yakho ngesigxivizo somunwe."</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2" msgid="6897989352716156176">"I-<xliff:g id="FINGERPRINT_0">%1$s</xliff:g> kanye ne-<xliff:g id="FINGERPRINT_1">%2$s</xliff:g> ibingasebenzi kahle futhi isuliwe. Phinde uyisethe ukuze uvule ifoni yakho ngesigxivizo somunwe wakho"</string> + <string name="face_dangling_notification_title" msgid="947852541060975473">"Setha Ukuvula Ngobuso futhi"</string> + <string name="face_dangling_notification_msg" msgid="8806849376915541655">"Imodeli yobuso yakho ibingasebenzi kahle futhi isuliwe. Phinde uyisethe ukuze uvule ifoni yakho ngobuso."</string> + <string name="biometric_dangling_notification_action_set_up" msgid="8246885009807817961">"Setha"</string> + <string name="biometric_dangling_notification_action_not_now" msgid="8095249216864443491">"Hhayi manje"</string> </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 37d39a752c65..405324bf76af 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1346,6 +1346,51 @@ <attr name="materialColorTertiary" format="color"/> <!-- The error color for the app, intended to draw attention to error conditions. @hide --> <attr name="materialColorError" format="color"/> + + <!-- System Custom Tokens--> + <!-- @hide --> + <attr name="customColorWidgetBackground" format="color"/> + <!-- @hide --> + <attr name="customColorClockHour" format="color"/> + <!-- @hide --> + <attr name="customColorClockMinute" format="color"/> + <!-- @hide --> + <attr name="customColorClockSecond" format="color"/> + <!-- @hide --> + <attr name="customColorThemeApp" format="color"/> + <!-- @hide --> + <attr name="customColorOnThemeApp" format="color"/> + <!-- @hide --> + <attr name="customColorThemeAppRing" format="color"/> + <!-- @hide --> + <attr name="customColorOnThemeAppRing" format="color"/> + <!-- @hide --> + <attr name="customColorBrandA" format="color"/> + <!-- @hide --> + <attr name="customColorBrandB" format="color"/> + <!-- @hide --> + <attr name="customColorBrandC" format="color"/> + <!-- @hide --> + <attr name="customColorBrandD" format="color"/> + <!-- @hide --> + <attr name="customColorUnderSurface" format="color"/> + <!-- @hide --> + <attr name="customColorShadeActive" format="color"/> + <!-- @hide --> + <attr name="customColorOnShadeActive" format="color"/> + <!-- @hide --> + <attr name="customColorOnShadeActiveVariant" format="color"/> + <!-- @hide --> + <attr name="customColorShadeInactive" format="color"/> + <!-- @hide --> + <attr name="customColorOnShadeInactive" format="color"/> + <!-- @hide --> + <attr name="customColorOnShadeInactiveVariant" format="color"/> + <!-- @hide --> + <attr name="customColorShadeDisabled" format="color"/> + <!-- @hide --> + <attr name="customColorOverviewBackground" format="color"/> + </declare-styleable> <!-- **************************************************************** --> @@ -2544,6 +2589,8 @@ <li>The framework will set {@link android.R.attr#statusBarColor}, {@link android.R.attr#navigationBarColor}, and {@link android.R.attr#navigationBarDividerColor} to transparent. + <li>The frameworks will send Configuration no longer considering system insets. + The Configuration will be stable regardless of the system insets change. </ul> <p>If this is true, the edge-to-edge enforcement won't be applied. However, this diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 5e900f773a65..27b756d46b12 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -842,7 +842,8 @@ that created the task, and therefore there will only be one instance of this activity in a task. In contrast to the {@code singleTask} launch mode, this activity can be started in multiple instances in different tasks if the - {@code FLAG_ACTIVITY_MULTIPLE_TASK} or {@code FLAG_ACTIVITY_NEW_DOCUMENT} is set.--> + {@code FLAG_ACTIVITY_MULTIPLE_TASK} or {@code FLAG_ACTIVITY_NEW_DOCUMENT} is set. + This enum value is introduced in API level 31. --> <enum name="singleInstancePerTask" value="4" /> </attr> <!-- Specify the orientation an activity should be run in. If not diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index e6719195565e..5e039b5e958d 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -581,6 +581,51 @@ <color name="system_on_tertiary_fixed">#FFFFFF</color> <color name="system_on_tertiary_fixed_variant">#FFFFFF</color> + <!--Colors used in Android system, from design system. These values can be overlaid at runtime + by OverlayManager RROs.--> + <color name="system_widget_background_light">#EEF0FF</color> + <color name="system_clock_hour_light">#1D2435</color> + <color name="system_clock_minute_light">#20386A</color> + <color name="system_clock_second_light">#000000</color> + <color name="system_theme_app_light">#2F4578</color> + <color name="system_on_theme_app_light">#D6DFFF</color> + <color name="system_theme_app_ring_light">#94AAE4</color> + <color name="system_on_theme_app_ring_light">#FDD7FA</color> + <color name="system_brand_a_light">#3A5084</color> + <color name="system_brand_b_light">#6E7488</color> + <color name="system_brand_c_light">#6076AC</color> + <color name="system_brand_d_light">#8C6D8C</color> + <color name="system_under_surface_light">#000000</color> + <color name="system_shade_active_light">#D9E2FF</color> + <color name="system_on_shade_active_light">#152E60</color> + <color name="system_on_shade_active_variant_light">#2F4578</color> + <color name="system_shade_inactive_light">#2F3036</color> + <color name="system_on_shade_inactive_light">#E1E2EC</color> + <color name="system_on_shade_inactive_variant_light">#C5C6D0</color> + <color name="system_shade_disabled_light">#0C0E13</color> + <color name="system_overview_background_light">#50525A</color> + <color name="system_widget_background_dark">#152E60</color> + <color name="system_clock_hour_dark">#9AA0B6</color> + <color name="system_clock_minute_dark">#D8E1FF</color> + <color name="system_clock_second_dark">#FFFFFF</color> + <color name="system_theme_app_dark">#D9E2FF</color> + <color name="system_on_theme_app_dark">#304679</color> + <color name="system_theme_app_ring_dark">#94AAE4</color> + <color name="system_on_theme_app_ring_dark">#E0BBDD</color> + <color name="system_brand_a_dark">#90A6DF</color> + <color name="system_brand_b_dark">#A4ABC1</color> + <color name="system_brand_c_dark">#7A90C8</color> + <color name="system_brand_d_dark">#A886A6</color> + <color name="system_under_surface_dark">#000000</color> + <color name="system_shade_active_dark">#D9E2FF</color> + <color name="system_on_shade_active_dark">#001945</color> + <color name="system_on_shade_active_variant_dark">#2F4578</color> + <color name="system_shade_inactive_dark">#2F3036</color> + <color name="system_on_shade_inactive_dark">#E1E2EC</color> + <color name="system_on_shade_inactive_variant_dark">#C5C6D0</color> + <color name="system_shade_disabled_dark">#0C0E13</color> + <color name="system_overview_background_dark">#C5C6D0</color> + <!-- Accessibility shortcut icon background color --> <color name="accessibility_feature_background">#5F6368</color> <!-- Google grey 700 --> <color name="accessibility_magnification_background">#F50D60</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 877d11e0a09a..0676f721c469 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,15 @@ <!-- 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> + + <!-- The DeviceConfig namespace for the default system on-device sandboxed inference service. --> + <string name="config_defaultOnDeviceIntelligenceDeviceConfigNamespace" 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> @@ -6093,6 +6115,18 @@ <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. --> <bool name="config_letterboxIsEducationEnabled">false</bool> + <!-- The width in dp to use to detect vertical thin letterboxing. + If W is the available width and w is the letterbox width, an app + is thin letterboxed if the value here is < (W - w) / 2 + If the value is < 0 the thin letterboxing policy is disabled --> + <dimen name="config_letterboxThinLetterboxWidthDp">-1dp</dimen> + + <!-- The height in dp to use to detect horizontal thin letterboxing + If H is the available height and h is the letterbox height, an app + is thin letterboxed if the value here is < (H - h) / 2 + If the value is < 0 the thin letterboxing policy is disabled --> + <dimen name="config_letterboxThinLetterboxHeightDp">-1dp</dimen> + <!-- Default min aspect ratio for unresizable apps which are eligible for size compat mode. Values <= 1.0 will be ignored. Activity min/max aspect ratio restrictions will still be espected so this override can control the maximum screen area that can be occupied by @@ -6919,9 +6953,6 @@ an app is not changed during subsequent reboots. --> <bool name="config_stopSystemPackagesByDefault">true</bool> - <!-- Whether to show weather on the lock screen by default. --> - <bool name="config_lockscreenWeatherEnabledByDefault">false</bool> - <!-- Whether we should persist the brightness value in nits for the default display even if the underlying display device changes. --> <bool name="config_persistBrightnessNitsForDefaultDisplay">false</bool> @@ -7039,6 +7070,9 @@ event gets ignored. --> <integer name="config_defaultMinEmergencyGestureTapDurationMillis">200</integer> + <!-- Control whether to enable CallMetadataSyncInCallService. --> + <bool name="config_enableContextSyncInCall">false</bool> + <!-- Whether the system uses auto-suspend mode. --> <bool name="config_useAutoSuspend">true</bool> </resources> diff --git a/core/res/res/values/config_battery_stats.xml b/core/res/res/values/config_battery_stats.xml index 8d9736273145..80cf0881e8cc 100644 --- a/core/res/res/values/config_battery_stats.xml +++ b/core/res/res/values/config_battery_stats.xml @@ -27,16 +27,18 @@ <!-- Whether to reset Battery Stats on unplug if the battery was significantly charged --> <bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool> - <!-- CPU power stats collection throttle period in milliseconds. Since power stats collection - is a relatively expensive operation, this throttle period may need to be adjusted for low-power - devices--> - <integer name="config_defaultPowerStatsThrottlePeriodCpu">60000</integer> - - <!-- Mobile Radio power stats collection throttle period in milliseconds. --> - <integer name="config_defaultPowerStatsThrottlePeriodMobileRadio">3600000</integer> - - <!-- Mobile Radio power stats collection throttle period in milliseconds. --> - <integer name="config_defaultPowerStatsThrottlePeriodWifi">3600000</integer> + <!-- Power stats collection throttle periods in milliseconds. Since power stats collection + is a relatively expensive operation, these throttle period may need to be adjusted for low-power + devices. + + The syntax of this config string is as follows: + <pre> + power-component-name1:throttle-period-millis1 power-component-name2:throttle-period-ms2 ... + </pre> + Use "*" for the power-component-name to represent the default for all power components + not mentioned by name. + --> + <string name="config_powerStatsThrottlePeriods">cpu:60000 *:300000</string> <!-- PowerStats aggregation period in milliseconds. This is the interval at which the power stats aggregation procedure is performed and the results stored in PowerStatsStore. --> diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index cc02a7e377c5..e420ffe68e4c 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -365,4 +365,23 @@ <item>xcap</item> </string-array> <java-symbol type="array" name="config_force_cellular_transport_capabilities" /> + + <!-- The time duration in millis after which DemoSimulator will move to CONNECTED state from + NOT_CONNECTED state if the device is aligned to satellite. + --> + <integer name="config_demo_pointing_aligned_duration_millis">15000</integer> + <java-symbol type="integer" name="config_demo_pointing_aligned_duration_millis" /> + + <!-- The time duration in millis after which DemoSimulator will move to NOT_CONNECTED state from + CONNECTED state if the device is not aligned to satellite. + --> + <integer name="config_demo_pointing_not_aligned_duration_millis">30000</integer> + <java-symbol type="integer" name="config_demo_pointing_not_aligned_duration_millis" /> + + <!-- Boolean indicating whether Telephony should wait for device alignment with satellite + before sending or receiving datagrams in demo mode. + --> + <bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool> + <java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" /> + </resources> diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 52ce9936787f..b885e03bc098 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -846,6 +846,12 @@ <dimen name="conversation_face_pile_protection_width">2dp</dimen> <!-- The width of the protection of the face pile layout when expanded--> <dimen name="conversation_face_pile_protection_width_expanded">@dimen/conversation_face_pile_protection_width</dimen> + <!-- size of the compact face pile --> + <dimen name="conversation_compact_face_pile_size">24dp</dimen> + <!-- size of the face pile avatar --> + <dimen name="conversation_compact_face_pile_avatar_size">17dp</dimen> + <!-- size of the face pile protection --> + <dimen name="conversation_compact_face_pile_protection_width">1dp</dimen> <!-- The padding of the expanded message container--> <dimen name="expanded_group_conversation_message_padding">32dp</dimen> <!-- The stroke width of the ring used to visually mark a conversation as important --> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1fca4f859294..2da5e9a90dbb 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -794,6 +794,9 @@ <!-- The divider symbol between different parts of the notification header including spaces. not translatable [CHAR LIMIT=3] --> <string name="notification_header_divider_symbol_with_spaces" translatable="false">" • "</string> + <!-- Text for inline reply button for compact conversation heads ups --> + <string name="notification_compact_heads_up_reply">Reply</string> + <!-- Text shown in place of notification contents when the notification is hidden on a secure lockscreen --> <string name="notification_hidden_text">New notification</string> @@ -6484,4 +6487,23 @@ ul.</string> <string name="satellite_notification_how_it_works">How it works</string> <!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. --> <string name="unarchival_session_app_label">Pending...</string> + + <!-- Fingerprint dangling notification title --> + <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string> + <!-- Fingerprint dangling notification content for only 1 fingerprint deleted --> + <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted</string> + <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted --> + <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted</string> + <!-- Fingerprint dangling notification content for only 1 fingerprint deleted and no fingerprint left--> + <string name="fingerprint_dangling_notification_msg_all_deleted_1"><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> + <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted and no fingerprint left --> + <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint.</string> + <!-- Face dangling notification title --> + <string name="face_dangling_notification_title">Set up Face Unlock again</string> + <!-- Face dangling notification content --> + <string name="face_dangling_notification_msg">Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face.</string> + <!-- Biometric dangling notification "set up" action button --> + <string name="biometric_dangling_notification_action_set_up">Set up</string> + <!-- Biometric dangling notification "Not now" action button --> + <string name="biometric_dangling_notification_action_not_now">Not now</string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b3d8f392700d..5a3eaeb8327b 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" /> @@ -1387,6 +1389,7 @@ <java-symbol type="drawable" name="platlogo" /> <java-symbol type="drawable" name="stat_notify_sync_error" /> <java-symbol type="drawable" name="stat_notify_wifi_in_range" /> + <java-symbol type="drawable" name="ic_thread_network" /> <java-symbol type="drawable" name="ic_wifi_signal_0" /> <java-symbol type="drawable" name="ic_wifi_signal_1" /> <java-symbol type="drawable" name="ic_wifi_signal_2" /> @@ -2350,6 +2353,7 @@ <java-symbol type="layout" name="notification_template_material_base" /> <java-symbol type="layout" name="notification_template_material_heads_up_base" /> <java-symbol type="layout" name="notification_template_material_compact_heads_up_base" /> + <java-symbol type="layout" name="notification_template_material_messaging_compact_heads_up" /> <java-symbol type="layout" name="notification_template_material_big_base" /> <java-symbol type="layout" name="notification_template_material_big_picture" /> <java-symbol type="layout" name="notification_template_material_inbox" /> @@ -3436,7 +3440,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" /> @@ -3625,6 +3629,7 @@ <java-symbol type="drawable" name="lockscreen_selected" /> <java-symbol type="string" name="notification_header_divider_symbol_with_spaces" /> + <java-symbol type="string" name="notification_compact_heads_up_reply" /> <java-symbol type="color" name="notification_primary_text_color_light" /> <java-symbol type="color" name="notification_primary_text_color_dark" /> @@ -3940,6 +3945,9 @@ <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_defaultOnDeviceIntelligenceDeviceConfigNamespace" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> @@ -4517,6 +4525,7 @@ <java-symbol type="id" name="expand_button_container" /> <java-symbol type="id" name="expand_button_a11y_container" /> <java-symbol type="id" name="expand_button_touch_container" /> + <java-symbol type="id" name="reply_action_container" /> <java-symbol type="id" name="messaging_group_content_container" /> <java-symbol type="id" name="expand_button_and_content_container" /> <java-symbol type="id" name="conversation_header" /> @@ -4534,6 +4543,9 @@ <java-symbol type="dimen" name="conversation_avatar_size_group_expanded" /> <java-symbol type="dimen" name="conversation_face_pile_avatar_size" /> <java-symbol type="dimen" name="conversation_face_pile_avatar_size_group_expanded" /> + <java-symbol type="dimen" name="conversation_compact_face_pile_size" /> + <java-symbol type="dimen" name="conversation_compact_face_pile_avatar_size" /> + <java-symbol type="dimen" name="conversation_compact_face_pile_protection_width" /> <java-symbol type="dimen" name="conversation_face_pile_protection_width" /> <java-symbol type="dimen" name="conversation_face_pile_protection_width_expanded" /> <java-symbol type="dimen" name="conversation_badge_protrusion_group_expanded" /> @@ -4716,6 +4728,8 @@ <java-symbol type="integer" name="config_letterboxDefaultPositionForTabletopModeReachability" /> <java-symbol type="bool" name="config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled" /> <java-symbol type="bool" name="config_letterboxIsEducationEnabled" /> + <java-symbol type="dimen" name="config_letterboxThinLetterboxWidthDp" /> + <java-symbol type="dimen" name="config_letterboxThinLetterboxHeightDp" /> <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" /> <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" /> <java-symbol type="bool" name="config_letterboxIsDisplayAspectRatioForFixedOrientationLetterboxEnabled" /> @@ -5214,9 +5228,6 @@ <java-symbol type="bool" name="config_hotspotNetworksEnabledForService"/> <java-symbol type="bool" name="config_knownNetworksEnabledForService"/> - <!-- Whether to show weather on the lockscreen by default. --> - <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" /> - <!-- For keyboard notification --> <java-symbol type="string" name="keyboard_layout_notification_selected_title"/> <java-symbol type="string" name="keyboard_layout_notification_one_selected_message"/> @@ -5233,9 +5244,7 @@ <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" /> <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" /> - <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodCpu" /> - <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodMobileRadio" /> - <java-symbol type="integer" name="config_defaultPowerStatsThrottlePeriodWifi" /> + <java-symbol type="string" name="config_powerStatsThrottlePeriods" /> <java-symbol type="integer" name="config_powerStatsAggregationPeriod" /> <java-symbol type="integer" name="config_aggregatedPowerStatsSpanDuration" /> @@ -5285,6 +5294,71 @@ <java-symbol name="materialColorTertiary" type="attr"/> <java-symbol name="materialColorError" type="attr"/> + <java-symbol name="customColorWidgetBackground" type="attr"/> + <java-symbol name="customColorClockHour" type="attr"/> + <java-symbol name="customColorClockMinute" type="attr"/> + <java-symbol name="customColorClockSecond" type="attr"/> + <java-symbol name="customColorThemeApp" type="attr"/> + <java-symbol name="customColorOnThemeApp" type="attr"/> + <java-symbol name="customColorThemeAppRing" type="attr"/> + <java-symbol name="customColorOnThemeAppRing" type="attr"/> + <java-symbol name="customColorBrandA" type="attr"/> + <java-symbol name="customColorBrandB" type="attr"/> + <java-symbol name="customColorBrandC" type="attr"/> + <java-symbol name="customColorBrandD" type="attr"/> + <java-symbol name="customColorUnderSurface" type="attr"/> + <java-symbol name="customColorShadeActive" type="attr"/> + <java-symbol name="customColorOnShadeActive" type="attr"/> + <java-symbol name="customColorOnShadeActiveVariant" type="attr"/> + <java-symbol name="customColorShadeInactive" type="attr"/> + <java-symbol name="customColorOnShadeInactive" type="attr"/> + <java-symbol name="customColorOnShadeInactiveVariant" type="attr"/> + <java-symbol name="customColorShadeDisabled" type="attr"/> + <java-symbol name="customColorOverviewBackground" type="attr"/> + + <java-symbol name="system_widget_background_light" type="color"/> + <java-symbol name="system_clock_hour_light" type="color"/> + <java-symbol name="system_clock_minute_light" type="color"/> + <java-symbol name="system_clock_second_light" type="color"/> + <java-symbol name="system_theme_app_light" type="color"/> + <java-symbol name="system_on_theme_app_light" type="color"/> + <java-symbol name="system_theme_app_ring_light" type="color"/> + <java-symbol name="system_on_theme_app_ring_light" type="color"/> + <java-symbol name="system_brand_a_light" type="color"/> + <java-symbol name="system_brand_b_light" type="color"/> + <java-symbol name="system_brand_c_light" type="color"/> + <java-symbol name="system_brand_d_light" type="color"/> + <java-symbol name="system_under_surface_light" type="color"/> + <java-symbol name="system_shade_active_light" type="color"/> + <java-symbol name="system_on_shade_active_light" type="color"/> + <java-symbol name="system_on_shade_active_variant_light" type="color"/> + <java-symbol name="system_shade_inactive_light" type="color"/> + <java-symbol name="system_on_shade_inactive_light" type="color"/> + <java-symbol name="system_on_shade_inactive_variant_light" type="color"/> + <java-symbol name="system_shade_disabled_light" type="color"/> + <java-symbol name="system_overview_background_light" type="color"/> + <java-symbol name="system_widget_background_dark" type="color"/> + <java-symbol name="system_clock_hour_dark" type="color"/> + <java-symbol name="system_clock_minute_dark" type="color"/> + <java-symbol name="system_clock_second_dark" type="color"/> + <java-symbol name="system_theme_app_dark" type="color"/> + <java-symbol name="system_on_theme_app_dark" type="color"/> + <java-symbol name="system_theme_app_ring_dark" type="color"/> + <java-symbol name="system_on_theme_app_ring_dark" type="color"/> + <java-symbol name="system_brand_a_dark" type="color"/> + <java-symbol name="system_brand_b_dark" type="color"/> + <java-symbol name="system_brand_c_dark" type="color"/> + <java-symbol name="system_brand_d_dark" type="color"/> + <java-symbol name="system_under_surface_dark" type="color"/> + <java-symbol name="system_shade_active_dark" type="color"/> + <java-symbol name="system_on_shade_active_dark" type="color"/> + <java-symbol name="system_on_shade_active_variant_dark" type="color"/> + <java-symbol name="system_shade_inactive_dark" type="color"/> + <java-symbol name="system_on_shade_inactive_dark" type="color"/> + <java-symbol name="system_on_shade_inactive_variant_dark" type="color"/> + <java-symbol name="system_shade_disabled_dark" type="color"/> + <java-symbol name="system_overview_background_dark" type="color"/> + <java-symbol type="attr" name="actionModeUndoDrawable" /> <java-symbol type="attr" name="actionModeRedoDrawable" /> @@ -5429,4 +5503,15 @@ <!-- For PowerManagerService to determine whether to use auto-suspend mode --> <java-symbol type="bool" name="config_useAutoSuspend" /> + + <!-- Biometric dangling notification strings --> + <java-symbol type="string" name="fingerprint_dangling_notification_title" /> + <java-symbol type="string" name="fingerprint_dangling_notification_msg_1" /> + <java-symbol type="string" name="fingerprint_dangling_notification_msg_2" /> + <java-symbol type="string" name="fingerprint_dangling_notification_msg_all_deleted_1" /> + <java-symbol type="string" name="fingerprint_dangling_notification_msg_all_deleted_2" /> + <java-symbol type="string" name="face_dangling_notification_title" /> + <java-symbol type="string" name="face_dangling_notification_msg" /> + <java-symbol type="string" name="biometric_dangling_notification_action_set_up" /> + <java-symbol type="string" name="biometric_dangling_notification_action_not_now" /> </resources> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index ee191449ac35..24d493867906 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -284,6 +284,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" /> @@ -380,6 +402,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar. This theme @@ -475,6 +519,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar and @@ -572,6 +638,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} that has no title bar and translucent @@ -668,6 +756,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- DeviceDefault theme for dialog windows and activities. This changes the window to be @@ -772,6 +882,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Dialog} that has a nice minimum width for a @@ -867,6 +999,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar --> @@ -961,6 +1115,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width @@ -1056,6 +1232,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. --> @@ -1167,6 +1365,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- DeviceDefault theme for a window without an action bar that will be displayed either @@ -1263,6 +1483,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- DeviceDefault theme for a presentation window on a secondary display. --> @@ -1357,6 +1599,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- DeviceDefault theme for panel windows. This removes all extraneous window @@ -1453,6 +1717,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear @@ -1548,6 +1834,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- DeviceDefault theme for windows that want to have the user's selected wallpaper appear @@ -1643,6 +1951,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- DeviceDefault style for input methods, which is used by the @@ -1738,6 +2068,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- DeviceDefault style for input methods, which is used by the @@ -1833,6 +2185,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert"> @@ -1928,6 +2302,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Theme for the dialog shown when an app crashes or ANRs. --> @@ -2028,6 +2424,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame"> @@ -2121,6 +2539,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <!-- Variant of {@link #Theme_DeviceDefault} with a light-colored style --> @@ -2352,6 +2792,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of the DeviceDefault (light) theme that has a solid (opaque) action bar with an @@ -2447,6 +2909,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar --> @@ -2541,6 +3025,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar. @@ -2636,6 +3142,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} with no action bar and no status bar @@ -2733,6 +3261,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light} that has no title bar and translucent @@ -2829,6 +3379,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- DeviceDefault light theme for dialog windows and activities. This changes the window to be @@ -2931,6 +3503,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} that has a nice minimum width for a @@ -3029,6 +3623,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog} without an action bar --> @@ -3126,6 +3742,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Light_Dialog_NoActionBar} that has a nice minimum @@ -3224,6 +3862,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of Theme.DeviceDefault.Dialog that has a fixed size. --> @@ -3303,6 +3963,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of Theme.DeviceDefault.Dialog.NoActionBar that has a fixed size. --> @@ -3382,6 +4064,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- DeviceDefault light theme for a window that will be displayed either full-screen on smaller @@ -3480,6 +4184,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- DeviceDefault light theme for a window without an action bar that will be displayed either @@ -3579,6 +4305,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- DeviceDefault light theme for a presentation window on a secondary display. --> @@ -3676,6 +4424,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- DeviceDefault light theme for panel windows. This removes all extraneous window @@ -3772,6 +4542,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.Light.Dialog.Alert" parent="Theme.Material.Light.Dialog.Alert"> @@ -3867,6 +4659,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.Dialog.Alert.DayNight" parent="Theme.DeviceDefault.Light.Dialog.Alert" /> @@ -3962,6 +4776,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice"> @@ -4055,6 +4891,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- DeviceDefault theme for a window that should look like the Settings app. --> @@ -4156,6 +5014,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.SystemUI" parent="Theme.DeviceDefault.Light"> @@ -4238,6 +5118,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.SystemUI.Dialog" parent="Theme.DeviceDefault.Light.Dialog"> @@ -4312,6 +5214,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Variant of {@link #Theme_DeviceDefault_Settings_Dark} with no action bar --> @@ -4407,6 +5331,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <style name="Theme.DeviceDefault.Settings.DialogBase" parent="Theme.Material.Light.BaseDialog"> @@ -4486,6 +5432,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.Settings.Dialog" parent="Theme.DeviceDefault.Settings.DialogBase"> @@ -4605,6 +5573,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert"> @@ -4702,6 +5692,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" /> @@ -4825,6 +5837,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <style name="ThemeOverlay.DeviceDefault.Accent.Light"> @@ -4878,6 +5912,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <!-- Theme overlay that replaces colorAccent with the colorAccent from {@link #Theme_DeviceDefault_DayNight}. --> @@ -4935,6 +5991,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <style name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" parent="Theme.DeviceDefault.NoActionBar.Fullscreen"> @@ -4988,6 +6066,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_light</item> <item name="materialColorTertiary">@color/system_tertiary_light</item> <item name="materialColorError">@color/system_error_light</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_light</item> + <item name="customColorClockHour">@color/system_clock_hour_light</item> + <item name="customColorClockMinute">@color/system_clock_minute_light</item> + <item name="customColorClockSecond">@color/system_clock_second_light</item> + <item name="customColorThemeApp">@color/system_theme_app_light</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_light</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_light</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_light</item> + <item name="customColorBrandA">@color/system_brand_a_light</item> + <item name="customColorBrandB">@color/system_brand_b_light</item> + <item name="customColorBrandC">@color/system_brand_c_light</item> + <item name="customColorBrandD">@color/system_brand_d_light</item> + <item name="customColorUnderSurface">@color/system_under_surface_light</item> + <item name="customColorShadeActive">@color/system_shade_active_light</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_light</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_light</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_light</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_light</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_light</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_light</item> + <item name="customColorOverviewBackground">@color/system_overview_background_light</item> </style> <style name="Theme.DeviceDefault.Notification" parent="@style/Theme.Material.Notification"> @@ -5052,6 +6152,28 @@ easier. <item name="materialColorSecondary">@color/system_secondary_dark</item> <item name="materialColorTertiary">@color/system_tertiary_dark</item> <item name="materialColorError">@color/system_error_dark</item> + + <item name="customColorWidgetBackground">@color/system_widget_background_dark</item> + <item name="customColorClockHour">@color/system_clock_hour_dark</item> + <item name="customColorClockMinute">@color/system_clock_minute_dark</item> + <item name="customColorClockSecond">@color/system_clock_second_dark</item> + <item name="customColorThemeApp">@color/system_theme_app_dark</item> + <item name="customColorOnThemeApp">@color/system_on_theme_app_dark</item> + <item name="customColorThemeAppRing">@color/system_theme_app_ring_dark</item> + <item name="customColorOnThemeAppRing">@color/system_on_theme_app_ring_dark</item> + <item name="customColorBrandA">@color/system_brand_a_dark</item> + <item name="customColorBrandB">@color/system_brand_b_dark</item> + <item name="customColorBrandC">@color/system_brand_c_dark</item> + <item name="customColorBrandD">@color/system_brand_d_dark</item> + <item name="customColorUnderSurface">@color/system_under_surface_dark</item> + <item name="customColorShadeActive">@color/system_shade_active_dark</item> + <item name="customColorOnShadeActive">@color/system_on_shade_active_dark</item> + <item name="customColorOnShadeActiveVariant">@color/system_on_shade_active_variant_dark</item> + <item name="customColorShadeInactive">@color/system_shade_inactive_dark</item> + <item name="customColorOnShadeInactive">@color/system_on_shade_inactive_dark</item> + <item name="customColorOnShadeInactiveVariant">@color/system_on_shade_inactive_variant_dark</item> + <item name="customColorShadeDisabled">@color/system_shade_disabled_dark</item> + <item name="customColorOverviewBackground">@color/system_overview_background_dark</item> </style> <style name="Theme.DeviceDefault.AutofillHalfScreenDialogList" parent="Theme.DeviceDefault.DayNight"> <item name="colorListDivider">@color/list_divider_opacity_device_default_light</item> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index 4d7c00991798..67cceb5d5343 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -60,6 +60,9 @@ <!-- Belgium: 4 digits, plus EU: http://www.mobileweb.be/en/mobileweb/sms-numberplan.asp --> <shortcode country="be" premium="\\d{4}" free="8\\d{3}|116\\d{3}" /> + <!-- Burkina Faso: 1-4 digits (standard system default, not country specific) --> + <shortcode country="bf" pattern="\\d{1,4}" free="3558" /> + <!-- Bulgaria: 4-5 digits, plus EU --> <shortcode country="bg" pattern="\\d{4,5}" premium="18(?:16|423)|19(?:1[56]|35)" free="116\\d{3}|1988|1490" /> @@ -175,8 +178,8 @@ <!-- Israel: 1-5 digits, known premium codes listed --> <shortcode country="il" pattern="\\d{1,5}" premium="4422|4545" free="37477|6681" /> - <!-- Iran: 4-6 digits, known premium codes listed --> - <shortcode country="ir" pattern="\\d{4,6}" free="700791|700792" /> + <!-- Iran: 4-8 digits, known premium codes listed --> + <shortcode country="ir" pattern="\\d{4,8}" free="700791|700792|100016|30008360" /> <!-- Italy: 5 digits (premium=41xxx,42xxx), plus EU: https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf --> @@ -352,7 +355,7 @@ <shortcode country="za" pattern="\\d{1,5}" free="44136|30791|36056|33009" /> <!-- Yemen --> - <shortcode country="ye" pattern="\\d{1,4}" free="5081" /> + <shortcode country="ye" pattern="\\d{1,4}" free="5079" /> <!-- Zimbabwe --> <shortcode country="zw" pattern="\\d{1,5}" free="33679" /> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 436ba15235c9..eb3c84a3ba0c 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -260,6 +260,7 @@ android_ravenwood_test { "src/com/android/internal/os/**/*.java", "src/com/android/internal/util/**/*.java", "src/com/android/internal/power/EnergyConsumerStatsTest.java", + "src/com/android/internal/ravenwood/**/*.java", // Pull in R.java from FrameworksCoreTests-resonly, not from FrameworksCoreTests, // to avoid having a dependency to FrameworksCoreTests. 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/app/servertransaction/ClientTransactionListenerControllerTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java index b6f4429aec8a..ee1d1e1b975c 100644 --- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java +++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionListenerControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -65,6 +66,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.RejectedExecutionException; import java.util.function.BiConsumer; /** @@ -221,4 +223,17 @@ public class ClientTransactionListenerControllerTest { 123 /* newDisplayId */, true /* shouldReportConfigChange*/); inOrder.verify(mController).onContextConfigurationPostChanged(context); } + + @Test + public void testDisplayListenerHandlerClosed() { + doReturn(123).when(mActivity).getDisplayId(); + doThrow(new RejectedExecutionException()).when(mController).onDisplayChanged(123); + + mController.onContextConfigurationPreChanged(mActivity); + mConfiguration.windowConfiguration.setMaxBounds(new Rect(0, 0, 100, 200)); + mController.onContextConfigurationPostChanged(mActivity); + + // No crash + verify(mController).onDisplayChanged(123); + } } 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/net/OWNERS b/core/tests/coretests/src/android/net/OWNERS index a779c00814cb..beb77dc8f4fd 100644 --- a/core/tests/coretests/src/android/net/OWNERS +++ b/core/tests/coretests/src/android/net/OWNERS @@ -1,4 +1,5 @@ include /services/core/java/com/android/server/net/OWNERS -per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com +per-file SSL*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS +per-file Uri* = varunshah@google.com diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 2a4ca79d997e..57cb1586bcd0 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -18,6 +18,7 @@ package android.net; import android.content.ContentUris; import android.os.Parcel; +import android.platform.test.annotations.AsbSecurityTest; import androidx.test.filters.SmallTest; @@ -86,6 +87,16 @@ public class UriTest extends TestCase { assertNull(u.getHost()); } + @AsbSecurityTest(cveBugId = 261721900) + @SmallTest + public void testSchemeSanitization() { + Uri uri = new Uri.Builder() + .scheme("http://https://evil.com:/te:st/") + .authority("google.com").path("one/way").build(); + assertEquals("httphttpsevil.com:/te:st/", uri.getScheme()); + assertEquals("httphttpsevil.com:/te:st/://google.com/one/way", uri.toString()); + } + @SmallTest public void testStringUri() { assertEquals("bob lee", diff --git a/core/tests/coretests/src/android/os/VintfObjectTest.java b/core/tests/coretests/src/android/os/VintfObjectTest.java index f34b8fd358d9..f81b31d0bd5a 100644 --- a/core/tests/coretests/src/android/os/VintfObjectTest.java +++ b/core/tests/coretests/src/android/os/VintfObjectTest.java @@ -16,16 +16,25 @@ package android.os; -import static org.junit.Assert.assertTrue; +import static com.google.common.truth.Truth.assertThat; + +import static java.util.stream.Collectors.toList; import android.platform.test.annotations.IgnoreUnderRavenwood; import android.platform.test.ravenwood.RavenwoodRule; +import android.util.Pair; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.xml.sax.InputSource; + +import java.io.StringReader; +import java.util.stream.Stream; + +import javax.xml.parsers.DocumentBuilderFactory; @RunWith(AndroidJUnit4.class) @IgnoreUnderRavenwood(blockedBy = VintfObject.class) @@ -39,12 +48,26 @@ public class VintfObjectTest { @Test public void testReport() { String[] xmls = VintfObject.report(); - assertTrue(xmls.length > 0); - // From /system/manifest.xml - assertTrue(String.join("", xmls).contains( - "<manifest version=\"1.0\" type=\"framework\">")); - // From /system/compatibility-matrix.xml - assertTrue(String.join("", xmls).contains( - "<compatibility-matrix version=\"1.0\" type=\"framework\"")); + + assertThat(Stream.of(xmls).map(xml -> rootAndType(xml)).collect(toList())) + .containsExactly( + Pair.create("manifest", "framework"), + Pair.create("compatibility-matrix", "framework"), + Pair.create("manifest", "device"), + Pair.create("compatibility-matrix", "device") + ); + } + + private static Pair<String, String> rootAndType(String content) { + try { + var factory = DocumentBuilderFactory.newInstance(); + var builder = factory.newDocumentBuilder(); + var inputSource = new InputSource(new StringReader(content)); + var document = builder.parse(inputSource); + var root = document.getDocumentElement(); + return Pair.create(root.getTagName(), root.getAttribute("type")); + } catch (Exception e) { + throw new RuntimeException(e); + } } } diff --git a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java index 6ae3d6597934..df9a89e07404 100644 --- a/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java +++ b/core/tests/coretests/src/android/tracing/perfetto/DataSourceTest.java @@ -674,8 +674,6 @@ public class DataSourceTest { protoOutputStream.write(SINGLE_INT, singleIntValue); protoOutputStream.end(payloadToken); protoOutputStream.end(forTestingToken); - - ctx.flush(); }), (args) -> {} ); diff --git a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java index 9c2a465c10a1..58e5be2b823d 100644 --- a/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java +++ b/core/tests/coretests/src/android/view/ImeBackAnimationControllerTest.java @@ -266,6 +266,23 @@ public class ImeBackAnimationControllerTest { }); } + @Test + public void testOnBackInvokedHidesImeEvenIfInsetsControlCancelled() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + // start back gesture + WindowInsetsAnimationControlListener animationControlListener = startBackGesture(); + + // simulate ImeBackAnimationController not receiving control (e.g. due to split screen) + animationControlListener.onCancelled(mWindowInsetsAnimationController); + + // commit back gesture + mBackAnimationController.onBackInvoked(); + + // verify that InsetsController#hide is called + verify(mInsetsController, times(1)).hide(ime()); + }); + } + private WindowInsetsAnimationControlListener startBackGesture() { // start back gesture mBackAnimationController.onBackStarted(new BackEvent(0f, 0f, 0f, EDGE_LEFT)); diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index 72f11198c6c0..0b1b40c8ba8b 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -17,6 +17,7 @@ package android.view; import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH; +import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT; import static android.view.Surface.FRAME_RATE_CATEGORY_LOW; import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL; import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE; @@ -584,6 +585,44 @@ public class ViewFrameRateTest { assertEquals(0f, mViewRoot.getLastPreferredFrameRate(), 0f); } + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY + }) + public void testQuickTouchBoost() throws Throwable { + mActivityRule.runOnUiThread(() -> { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + ViewGroup.LayoutParams layoutParams = mMovingView.getLayoutParams(); + layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT; + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + mMovingView.setLayoutParams(layoutParams); + mMovingView.setOnClickListener((v) -> {}); + }); + waitForFrameRateCategoryToSettle(); + mActivityRule.runOnUiThread(() -> assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory())); + int[] position = new int[2]; + mActivityRule.runOnUiThread(() -> { + mMovingView.getLocationOnScreen(position); + position[0] += mMovingView.getWidth() / 2; + position[1] += mMovingView.getHeight() / 2; + }); + final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + + long now = SystemClock.uptimeMillis(); + MotionEvent down = MotionEvent.obtain( + now, // downTime + now, // eventTime + MotionEvent.ACTION_DOWN, // action + position[0], // x + position[1], // y + 0 // metaState + ); + down.setSource(InputDevice.SOURCE_TOUCHSCREEN); + instrumentation.sendPointerSync(down); + assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory()); + } + private void runAfterDraw(@NonNull Runnable runnable) { Handler handler = new Handler(Looper.getMainLooper()); mAfterDrawLatch = new CountDownLatch(1); @@ -615,7 +654,6 @@ public class ViewFrameRateTest { for (int i = 0; i < 5 || mViewRoot.getIsFrameRateBoosting(); i++) { final CountDownLatch drawLatch = new CountDownLatch(1); - // Now that it is small, any invalidation should have a normal category ViewTreeObserver.OnDrawListener listener = drawLatch::countDown; mActivityRule.runOnUiThread(() -> { @@ -627,5 +665,12 @@ public class ViewFrameRateTest { mActivityRule.runOnUiThread( () -> mMovingView.getViewTreeObserver().removeOnDrawListener(listener)); } + // after boosting is complete, wait for one more draw cycle to ensure the boost isn't + // the last frame rate set + mActivityRule.runOnUiThread(() -> { + mMovingView.invalidate(); + runAfterDraw(() -> {}); + }); + waitForAfterDraw(); } } diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 0b0fd66f0744..b5c264c4ae5e 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -21,7 +21,9 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IBrailleDisplayController; import android.accessibilityservice.MagnificationConfig; +import android.annotation.EnforcePermission; import android.annotation.NonNull; +import android.annotation.RequiresNoPermission; import android.content.pm.ParceledListSlice; import android.graphics.Region; import android.hardware.usb.UsbDevice; @@ -216,16 +218,19 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon public void setAnimationScale(float scale) {} + @RequiresNoPermission @Override public void setInstalledAndEnabledServices(List<AccessibilityServiceInfo> infos) throws RemoteException { } + @RequiresNoPermission @Override public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() throws RemoteException { return null; } + @RequiresNoPermission @Override public void attachAccessibilityOverlayToDisplay( int interactionId, @@ -233,6 +238,7 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback) {} + @RequiresNoPermission @Override public void attachAccessibilityOverlayToWindow( int interactionId, @@ -240,14 +246,21 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback) {} + @EnforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT) @Override - public void connectBluetoothBrailleDisplay(String bluetoothAddress, - IBrailleDisplayController controller) {} + public void connectBluetoothBrailleDisplay( + String bluetoothAddress, IBrailleDisplayController controller) { + connectBluetoothBrailleDisplay_enforcePermission(); + } + @RequiresNoPermission @Override - public void connectUsbBrailleDisplay(UsbDevice usbDevice, - IBrailleDisplayController controller) {} + public void connectUsbBrailleDisplay( + UsbDevice usbDevice, IBrailleDisplayController controller) {} + @EnforcePermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) @Override - public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) {} + public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) { + setTestBrailleDisplayData_enforcePermission(); + } } diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index d54b862dfd34..50d7f59f70e9 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -384,11 +384,7 @@ public class WindowOnBackInvokedDispatcherTest { clearInvocations(mCallback1); callbackInfo.getCallback().onBackCancelled(); - waitForIdle(); - // verify onBackCancelled not yet called (since BackProgressAnimator animates - // progress to 0 first) - verify(mCallback1, never()).onBackCancelled(); // simulate start of new gesture while cancel animation is still running callbackInfo.getCallback().onBackStarted(mBackEvent); diff --git a/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java new file mode 100644 index 000000000000..4eccbe5f1dc4 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/content/res/OverlayConfigParserTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.content.res; + +import static com.android.internal.content.om.OverlayConfigParser.SysPropWrapper; + +import static org.junit.Assert.assertEquals; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.content.om.OverlayConfigParser; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class OverlayConfigParserTest { + @Test(expected = IllegalStateException.class) + public void testMergePropNotRoProp() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("${persist.value}/path", sysProp); + } + + @Test(expected = IllegalStateException.class) + public void testMergePropMissingEndBracket() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("${ro.value/path", sysProp); + } + + @Test(expected = IllegalStateException.class) + public void testMergeOnlyPropStart() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("path/${", sysProp); + } + + @Test(expected = IllegalStateException.class) + public void testMergePropInProp() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("path/${${ro.value}}", sysProp); + } + + /** + * The path is only allowed to contain one property. + */ + @Test(expected = IllegalStateException.class) + public void testMergePropMultipleProps() { + SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + OverlayConfigParser.expandProperty("${ro.value}/path${ro.value2}/path", sysProp); + } + + @Test + public void testMergePropOneProp() { + final SysPropWrapper sysProp = p -> { + if ("ro.value".equals(p)) { + return "dummy_value"; + } else { + return "invalid"; + } + }; + + // Property in the beginnig of the string + String result = OverlayConfigParser.expandProperty("${ro.value}/path", + sysProp); + assertEquals("dummy_value/path", result); + + // Property in the middle of the string + result = OverlayConfigParser.expandProperty("path/${ro.value}/file", + sysProp); + assertEquals("path/dummy_value/file", result); + + // Property at the of the string + result = OverlayConfigParser.expandProperty("path/${ro.value}", + sysProp); + assertEquals("path/dummy_value", result); + + // Property is the entire string + result = OverlayConfigParser.expandProperty("${ro.value}", + sysProp); + assertEquals("dummy_value", result); + } + + @Test + public void testMergePropNoProp() { + final SysPropWrapper sysProp = p -> { + return "dummy_value"; + }; + + final String path = "no_props/path"; + String result = OverlayConfigParser.expandProperty(path, sysProp); + assertEquals(path, result); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java index baab3b218746..4846ed27af22 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerStatsTest.java @@ -22,6 +22,7 @@ import android.os.BatteryConsumer; import android.os.Parcel; import android.os.PersistableBundle; import android.platform.test.ravenwood.RavenwoodRule; +import android.util.IndentingPrintWriter; import android.util.SparseArray; import android.util.Xml; @@ -31,6 +32,8 @@ import androidx.test.runner.AndroidJUnit4; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.google.common.truth.StringSubject; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -38,6 +41,7 @@ import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; @RunWith(AndroidJUnit4.class) @@ -56,6 +60,9 @@ public class PowerStatsTest { extras.putBoolean("hasPowerMonitor", true); SparseArray<String> stateLabels = new SparseArray<>(); stateLabels.put(0x0F, "idle"); + extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, "device:0[3]"); + extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, "state:0"); + extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, "a:0 b:1"); mDescriptor = new PowerStats.Descriptor(BatteryConsumer.POWER_COMPONENT_CPU, 3, stateLabels, 1, 2, extras); mRegistry.register(mDescriptor); @@ -191,4 +198,68 @@ public class PowerStatsTest { newParcel.setDataPosition(0); return newParcel; } + + @Test + public void formatForBatteryHistory() { + PowerStats stats = new PowerStats(mDescriptor); + stats.durationMs = 1234; + stats.stats[0] = 10; + stats.stats[1] = 20; + stats.stats[2] = 30; + stats.stateStats.put(0x0F, new long[]{16}); + stats.stateStats.put(0xF0, new long[]{17}); + stats.uidStats.put(42, new long[]{40, 50}); + stats.uidStats.put(99, new long[]{60, 70}); + + assertThat(stats.formatForBatteryHistory(" #")) + .isEqualTo("duration=1234 cpu=" + + "device: [10, 20, 30]" + + " (idle) state: 16" + + " (cpu-f0) state: 17" + + " #42: a: 40 b: 50" + + " #99: a: 60 b: 70"); + } + + @Test + public void dump() { + PowerStats stats = new PowerStats(mDescriptor); + stats.durationMs = 1234; + stats.stats[0] = 10; + stats.stats[1] = 20; + stats.stats[2] = 30; + stats.stateStats.put(0x0F, new long[]{16}); + stats.stateStats.put(0xF0, new long[]{17}); + stats.uidStats.put(42, new long[]{40, 50}); + stats.uidStats.put(99, new long[]{60, 70}); + + StringWriter sw = new StringWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(sw); + stats.dump(pw); + pw.flush(); + String dump = sw.toString(); + + assertThat(dump).contains("duration=1234"); + assertThat(dump).contains("device: [10, 20, 30]"); + assertThat(dump).contains("(idle) state: 16"); + assertThat(dump).contains("(cpu-f0) state: 17"); + assertThat(dump).contains("UID 42: a: 40 b: 50"); + assertThat(dump).contains("UID 99: a: 60 b: 70"); + } + + @Test + public void formatter() { + assertThatFormatted(new long[]{12, 34, 56}, "a:0 b:1[2]") + .isEqualTo("a: 12 b: [34, 56]"); + assertThatFormatted(new long[]{12, 0, 0}, "a:0? b:1[2]?") + .isEqualTo("a: 12"); + assertThatFormatted(new long[]{0, 34, 56}, "a:0? b:1[2]?") + .isEqualTo("b: [34, 56]"); + assertThatFormatted(new long[]{3141592, 2000000, 1414213}, "pi:0p sqrt:1[2]p") + .isEqualTo("pi: 3.14 sqrt: [2.00, 1.41]"); + } + + private static StringSubject assertThatFormatted(long[] stats, String format) { + return assertThat(new PowerStats.PowerStatsFormatter(format) + .format(stats)); + } } diff --git a/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java new file mode 100644 index 000000000000..d1ef61b2e365 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/ravenwood/RavenwoodEnvironmentTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.internal.ravenwood; + +import static junit.framework.TestCase.assertEquals; + +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class RavenwoodEnvironmentTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Test + public void testIsRunningOnRavenwood() { + assertEquals(RavenwoodRule.isUnderRavenwood(), + RavenwoodEnvironment.getInstance().isRunningOnRavenwood()); + } +} diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java index 1d91af57ca56..991ada89c51e 100644 --- a/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java +++ b/core/tests/packagemonitortests/src/com/android/internal/content/PackageMonitorTest.java @@ -328,8 +328,11 @@ public class PackageMonitorTest { verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); verify(spyPackageMonitor, times(1)) .onPackageUpdateStarted(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); - ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(spyPackageMonitor, times(1)) + .onPackageUpdateStartedWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), + argumentCaptor.capture()); + verify(spyPackageMonitor, times(1)).onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture()); Bundle capturedExtras = argumentCaptor.getValue(); @@ -362,11 +365,16 @@ public class PackageMonitorTest { spyPackageMonitor.doHandlePackageEvent(intent); verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); verify(spyPackageMonitor, times(1)) .onPackageUpdateStarted(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)) + .onPackageUpdateStartedWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), + argumentCaptor.capture()); verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME)); + verify(spyPackageMonitor, times(1)).onPackageModifiedWithExtras(eq(FAKE_PACKAGE_NAME), + argumentCaptor.capture()); - ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); verify(spyPackageMonitor, times(1)) .onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture()); Bundle capturedExtras = argumentCaptor.getValue(); @@ -399,12 +407,18 @@ public class PackageMonitorTest { spyPackageMonitor.doHandlePackageEvent(intent); verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); verify(spyPackageMonitor, times(1)) .onPackageRemoved(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); verify(spyPackageMonitor, times(1)) + .onPackageRemovedWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), + argumentCaptor.capture()); + verify(spyPackageMonitor, times(1)) .onPackageRemovedAllUsers(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)) + .onPackageRemovedAllUsersWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), + argumentCaptor.capture()); - ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); verify(spyPackageMonitor, times(1)).onPackageDisappearedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture()); Bundle capturedExtras = argumentCaptor.getValue(); @@ -436,11 +450,16 @@ public class PackageMonitorTest { spyPackageMonitor.doHandlePackageEvent(intent); verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); verify(spyPackageMonitor, times(1)) .onPackageUpdateFinished(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)) + .onPackageModifiedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture()); verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME)); + verify(spyPackageMonitor, times(1)) + .onPackageModifiedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture()); + - ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); verify(spyPackageMonitor, times(1)).onPackageAppearedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture()); Bundle capturedExtras = argumentCaptor.getValue(); @@ -472,8 +491,11 @@ public class PackageMonitorTest { verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); verify(spyPackageMonitor, times(1)) .onPackageAdded(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); - ArgumentCaptor<Bundle> argumentCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(spyPackageMonitor, times(1)) + .onPackageAddedWithExtras(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), + argumentCaptor.capture()); + verify(spyPackageMonitor, times(1)).onPackageAppearedWithExtras(eq(FAKE_PACKAGE_NAME), argumentCaptor.capture()); Bundle capturedExtras = argumentCaptor.getValue(); 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/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java index 4c00c1667e3c..9785ca7face5 100644 --- a/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java @@ -216,6 +216,46 @@ public class ProcFileReaderTest { } @Test + public void testBufferSizeWithConsecutiveDelimiters() throws Exception { + // Read numbers using very small buffer size, exercising fillBuf() + // Include more consecutive delimiters than the buffer size. + final ProcFileReader reader = + buildReader("1 21 3 41 5 61 7 81 9 10\n", 3); + + assertEquals(1, reader.nextInt()); + assertEquals(21, reader.nextInt()); + assertEquals(3, reader.nextInt()); + assertEquals(41, reader.nextInt()); + assertEquals(5, reader.nextInt()); + assertEquals(61, reader.nextInt()); + assertEquals(7, reader.nextInt()); + assertEquals(81, reader.nextInt()); + assertEquals(9, reader.nextInt()); + assertEquals(10, reader.nextInt()); + reader.finishLine(); + assertFalse(reader.hasMoreData()); + } + + @Test + public void testBufferSizeWithConsecutiveDelimitersAndMultipleLines() throws Exception { + final ProcFileReader reader = + buildReader("1 21 41 \n 5 7 81 \n 9 10 \n", 3); + + assertEquals(1, reader.nextInt()); + assertEquals(21, reader.nextInt()); + assertEquals(41, reader.nextInt()); + reader.finishLine(); + assertEquals(5, reader.nextInt()); + assertEquals(7, reader.nextInt()); + assertEquals(81, reader.nextInt()); + reader.finishLine(); + assertEquals(9, reader.nextInt()); + assertEquals(10, reader.nextInt()); + reader.finishLine(); + assertFalse(reader.hasMoreData()); + } + + @Test public void testIgnore() throws Exception { final ProcFileReader reader = buildReader("a b c\n"); diff --git a/data/etc/core.protolog.pb b/data/etc/core.protolog.pb Binary files differindex 000f6ef46c2c..b41a607e91c7 100644 --- a/data/etc/core.protolog.pb +++ b/data/etc/core.protolog.pb diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 9d1e5074dd3e..65615e62c3df 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -322,11 +322,11 @@ <library name="android.hidl.manager-V1.0-java" file="/system/framework/android.hidl.manager-V1.0-java.jar" /> - <!-- These are the standard packages that are white-listed to always have internet + <!-- These are the standard packages that are allowed to always have internet access while in power save mode, even if they aren't in the foreground. --> <allow-in-power-save package="com.android.providers.downloads" /> - <!-- These are the standard packages that are white-listed to always have internet + <!-- These are the standard packages that are allowed to always have internet access while in data mode, even if they aren't in the foreground. --> <allow-in-data-usage-save package="com.android.providers.downloads" /> @@ -338,7 +338,7 @@ <!-- Emergency app needs to run in the background to reliably provide safety features --> <allow-in-power-save package="com.android.emergency" /> - <!-- Whitelist system providers --> + <!-- Allow system providers --> <!-- Calendar provider needs alarms while in idle --> <allow-in-power-save package="com.android.providers.calendar" /> <allow-in-power-save-except-idle package="com.android.providers.contacts" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 01deb4957cd3..b93cd468118a 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -583,6 +583,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "7211222997110112110": { + "message": "Refreshing activity for freeform camera compatibility treatment, activityRecord=%s", + "level": "VERBOSE", + "group": "WM_DEBUG_STATES", + "at": "com\/android\/server\/wm\/ActivityRefresher.java" + }, "1665699123574159131": { "message": "Starting activity when config will change = %b", "level": "VERBOSE", @@ -1771,12 +1777,6 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" }, - "-7756685416834187936": { - "message": "Refreshing activity for camera compatibility treatment, activityRecord=%s", - "level": "VERBOSE", - "group": "WM_DEBUG_STATES", - "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java" - }, "-5176775281239247368": { "message": "Reverting orientation after camera compat force rotation", "level": "VERBOSE", diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml index ac1b06495832..a4ee82544b37 100644 --- a/data/fonts/font_fallback_cjkvf.xml +++ b/data/fonts/font_fallback_cjkvf.xml @@ -768,7 +768,7 @@ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> </family> <family lang="zh-Hans"> - <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin" + <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular" supportedAxes="wght"> NotoSansCJK-Regular.ttc <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400 @@ -780,7 +780,7 @@ </font> </family> <family lang="zh-Hant,zh-Bopo"> - <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin" + <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular" supportedAxes="wght"> NotoSansCJK-Regular.ttc <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400 @@ -792,7 +792,7 @@ </font> </family> <family lang="ja"> - <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin" + <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular" supportedAxes="wght"> NotoSansCJK-Regular.ttc <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400 @@ -810,7 +810,7 @@ </font> </family> <family lang="ko"> - <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin" + <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular" supportedAxes="wght"> NotoSansCJK-Regular.ttc <!-- The default instance of NotoSansCJK-Regular.ttc is wght=100, so specify wght=400 diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml index 9545ae718574..8cbc3000c250 100644 --- a/data/fonts/fonts_cjkvf.xml +++ b/data/fonts/fonts_cjkvf.xml @@ -1409,39 +1409,39 @@ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font> </family> <family lang="zh-Hans"> - <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="100"/> </font> - <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="200"/> </font> - <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="300"/> </font> - <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="700"/> </font> - <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="800"/> </font> - <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin"> + <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="900"/> </font> @@ -1450,39 +1450,39 @@ </font> </family> <family lang="zh-Hant,zh-Bopo"> - <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="100"/> </font> - <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="200"/> </font> - <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="300"/> </font> - <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="700"/> </font> - <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="800"/> </font> - <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin"> + <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="900"/> </font> @@ -1491,39 +1491,39 @@ </font> </family> <family lang="ja"> - <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="100"/> </font> - <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="200"/> </font> - <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="300"/> </font> - <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="700"/> </font> - <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="800"/> </font> - <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin"> + <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="900"/> </font> @@ -1542,39 +1542,39 @@ </font> </family> <family lang="ko"> - <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="100"/> </font> - <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="200"/> </font> - <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="300"/> </font> - <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="400"/> </font> - <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="500"/> </font> - <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="600"/> </font> - <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="700"/> </font> - <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="800"/> </font> - <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin"> + <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKJP-Regular"> NotoSansCJK-Regular.ttc <axis tag="wght" stylevalue="900"/> </font> diff --git a/data/keyboards/Android.bp b/data/keyboards/Android.bp index f15c1535a667..423b55bd85db 100644 --- a/data/keyboards/Android.bp +++ b/data/keyboards/Android.bp @@ -27,3 +27,27 @@ genrule { targets: ["droidcore"], }, } + +prebuilt_usr_keylayout { + name: "keylayout_data", + srcs: [ + "*.kl", + ], + no_full_install: true, +} + +prebuilt_usr_keychars { + name: "keychars_data", + srcs: [ + "*.kcm", + ], + no_full_install: true, +} + +prebuilt_usr_idc { + name: "idc_data", + srcs: [ + "*.idc", + ], + no_full_install: true, +} diff --git a/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index e8b4104a33bb..f8d3bffbe00b 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -438,9 +438,15 @@ key usage 0x0c0070 BRIGHTNESS_DOWN FALLBACK_USAGE_MAPPING key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP FALLBACK_USAGE_MAPPING key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN FALLBACK_USAGE_MAPPING key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING +key usage 0x0c00D9 EMOJI_PICKER FALLBACK_USAGE_MAPPING key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING +key usage 0x0c019F SETTINGS FALLBACK_USAGE_MAPPING key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING +key usage 0x0c0227 REFRESH FALLBACK_USAGE_MAPPING +key usage 0x0c029D LANGUAGE_SWITCH FALLBACK_USAGE_MAPPING +key usage 0x0c029F RECENT_APPS FALLBACK_USAGE_MAPPING +key usage 0x0c02A2 ALL_APPS FALLBACK_USAGE_MAPPING key usage 0x0d0044 STYLUS_BUTTON_PRIMARY FALLBACK_USAGE_MAPPING key usage 0x0d005a STYLUS_BUTTON_SECONDARY FALLBACK_USAGE_MAPPING diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index d915b746e0cc..1c2014183bb7 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -143,7 +143,7 @@ public class BitmapFactory { * the decoder will try to pick the best matching config based on the * system's screen depth, and characteristics of the original image such * as if it has per-pixel alpha (requiring a config that also does). - * + * * Image are loaded with the {@link Bitmap.Config#ARGB_8888} config by * default. */ @@ -183,7 +183,7 @@ public class BitmapFactory { /** * If true (which is the default), the resulting bitmap will have its - * color channels pre-multipled by the alpha channel. + * color channels pre-multiplied by the alpha channel. * * <p>This should NOT be set to false for images to be directly drawn by * the view system or through a {@link Canvas}. The view system and @@ -221,9 +221,9 @@ public class BitmapFactory { * if {@link #inScaled} is set (which it is by default} and this * density does not match {@link #inTargetDensity}, then the bitmap * will be scaled to the target density before being returned. - * + * * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, + * {@link BitmapFactory#decodeResource(Resources, int)}, * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, * and {@link BitmapFactory#decodeResourceStream} * will fill in the density associated with the resource. The other @@ -242,29 +242,29 @@ public class BitmapFactory { * This is used in conjunction with {@link #inDensity} and * {@link #inScaled} to determine if and how to scale the bitmap before * returning it. - * + * * <p>If this is 0, - * {@link BitmapFactory#decodeResource(Resources, int)}, + * {@link BitmapFactory#decodeResource(Resources, int)}, * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, * and {@link BitmapFactory#decodeResourceStream} * will fill in the density associated the Resources object's * DisplayMetrics. The other * functions will leave it as-is and no scaling for density will be * performed. - * + * * @see #inDensity * @see #inScreenDensity * @see #inScaled * @see android.util.DisplayMetrics#densityDpi */ public int inTargetDensity; - + /** * The pixel density of the actual screen that is being used. This is * purely for applications running in density compatibility code, where * {@link #inTargetDensity} is actually the density the application * sees rather than the real screen density. - * + * * <p>By setting this, you * allow the loading code to avoid scaling a bitmap that is currently * in the screen density up/down to the compatibility density. Instead, @@ -274,18 +274,18 @@ public class BitmapFactory { * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight * Bitmap.getScaledHeight} to account for any different between the * bitmap's density and the target's density. - * + * * <p>This is never set automatically for the caller by * {@link BitmapFactory} itself. It must be explicitly set, since the * caller must deal with the resulting bitmap in a density-aware way. - * + * * @see #inDensity * @see #inTargetDensity * @see #inScaled * @see android.util.DisplayMetrics#densityDpi */ public int inScreenDensity; - + /** * When this flag is set, if {@link #inDensity} and * {@link #inTargetDensity} are not 0, the @@ -345,7 +345,7 @@ public class BitmapFactory { * ignored. * * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, this - * field works in conjuction with inPurgeable. If inPurgeable is false, + * field works in conjunction with inPurgeable. If inPurgeable is false, * then this field is ignored. If inPurgeable is true, then this field * determines whether the bitmap can share a reference to the input * data (inputstream, array, etc.) or if it must make a deep copy. @@ -583,11 +583,11 @@ public class BitmapFactory { opts.inDensity = density; } } - + if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } - + return decodeStream(is, pad, opts); } @@ -611,8 +611,8 @@ public class BitmapFactory { public static Bitmap decodeResource(Resources res, int id, Options opts) { validate(opts); Bitmap bm = null; - InputStream is = null; - + InputStream is = null; + try { final TypedValue value = new TypedValue(); is = res.openRawResource(id, value); diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index e03a1daf7202..0b3e5456d81c 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -55,7 +55,7 @@ import java.lang.annotation.RetentionPolicy; * Canvas and Drawables</a> developer guide.</p></div> */ public class Canvas extends BaseCanvas { - private static int sCompatiblityVersion = 0; + private static int sCompatibilityVersion = 0; private static boolean sCompatibilityRestore = false; private static boolean sCompatibilitySetBitmap = false; @@ -74,7 +74,7 @@ public class Canvas extends BaseCanvas { // Maximum bitmap size as defined in Skia's native code // (see SkCanvas.cpp, SkDraw.cpp) - private static final int MAXMIMUM_BITMAP_SIZE = 32766; + private static final int MAXIMUM_BITMAP_SIZE = 32766; // Use a Holder to allow static initialization of Canvas in the boot image. private static class NoImagePreloadHolder { @@ -331,7 +331,7 @@ public class Canvas extends BaseCanvas { * @see #getMaximumBitmapHeight() */ public int getMaximumBitmapWidth() { - return MAXMIMUM_BITMAP_SIZE; + return MAXIMUM_BITMAP_SIZE; } /** @@ -342,7 +342,7 @@ public class Canvas extends BaseCanvas { * @see #getMaximumBitmapWidth() */ public int getMaximumBitmapHeight() { - return MAXMIMUM_BITMAP_SIZE; + return MAXIMUM_BITMAP_SIZE; } // the SAVE_FLAG constants must match their native equivalents @@ -423,7 +423,7 @@ public class Canvas extends BaseCanvas { public static final int ALL_SAVE_FLAG = 0x1F; private static void checkValidSaveFlags(int saveFlags) { - if (sCompatiblityVersion >= Build.VERSION_CODES.P + if (sCompatibilityVersion >= Build.VERSION_CODES.P && saveFlags != ALL_SAVE_FLAG) { throw new IllegalArgumentException( "Invalid Layer Save Flag - only ALL_SAVE_FLAGS is allowed"); @@ -845,7 +845,7 @@ public class Canvas extends BaseCanvas { } private static void checkValidClipOp(@NonNull Region.Op op) { - if (sCompatiblityVersion >= Build.VERSION_CODES.P + if (sCompatibilityVersion >= Build.VERSION_CODES.P && op != Region.Op.INTERSECT && op != Region.Op.DIFFERENCE) { throw new IllegalArgumentException( "Invalid Region.Op - only INTERSECT and DIFFERENCE are allowed"); @@ -1435,7 +1435,7 @@ public class Canvas extends BaseCanvas { } /*package*/ static void setCompatibilityVersion(int apiLevel) { - sCompatiblityVersion = apiLevel; + sCompatibilityVersion = apiLevel; sCompatibilityRestore = apiLevel < Build.VERSION_CODES.M; sCompatibilitySetBitmap = apiLevel < Build.VERSION_CODES.O; nSetCompatibilityVersion(apiLevel); diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java index 0f2f8797b896..c1edafc16274 100644 --- a/graphics/java/android/graphics/Color.java +++ b/graphics/java/android/graphics/Color.java @@ -289,6 +289,9 @@ import java.util.function.DoubleUnaryOperator; */ @AnyThread @SuppressAutoDoc +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodClassLoadHook( + android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK) public class Color { @ColorInt public static final int BLACK = 0xFF000000; @ColorInt public static final int DKGRAY = 0xFF444444; diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index a2319a53a659..4bc3ecebb067 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -135,6 +135,9 @@ import java.util.function.DoubleUnaryOperator; @AnyThread @SuppressWarnings("StaticInitializerReferencesSubClass") @SuppressAutoDoc +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodClassLoadHook( + android.ravenwood.annotation.RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK) public abstract class ColorSpace { /** * Standard CIE 1931 2° illuminant A, encoded in xyY. @@ -2490,9 +2493,16 @@ public abstract class ColorSpace { return mNativePtr; } - private static native long nativeGetNativeFinalizer(); - private static native long nativeCreate(float a, float b, float c, float d, - float e, float f, float g, float[] xyz); + /** + * These methods can't be put in the Rgb class directly, because ColorSpace's + * static initializer instantiates Rgb, whose constructor needs them, which is a variation + * of b/337329128. + */ + static class Native { + static native long nativeGetNativeFinalizer(); + static native long nativeCreate(float a, float b, float c, float d, + float e, float f, float g, float[] xyz); + } private static DoubleUnaryOperator generateOETF(TransferParameters function) { if (function.isHLGish()) { @@ -2959,7 +2969,7 @@ public abstract class ColorSpace { // This mimics the old code that was in native. float[] nativeTransform = adaptToIlluminantD50(mWhitePoint, mTransform); - mNativePtr = nativeCreate((float) mTransferParameters.a, + mNativePtr = Native.nativeCreate((float) mTransferParameters.a, (float) mTransferParameters.b, (float) mTransferParameters.c, (float) mTransferParameters.d, @@ -2975,7 +2985,7 @@ public abstract class ColorSpace { private static class NoImagePreloadHolder { public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry( - ColorSpace.Rgb.class.getClassLoader(), nativeGetNativeFinalizer(), 0); + ColorSpace.Rgb.class.getClassLoader(), Native.nativeGetNativeFinalizer(), 0); } /** diff --git a/graphics/java/android/graphics/ComposePathEffect.java b/graphics/java/android/graphics/ComposePathEffect.java index 3fc9eb5aea15..7d59ecea948e 100644 --- a/graphics/java/android/graphics/ComposePathEffect.java +++ b/graphics/java/android/graphics/ComposePathEffect.java @@ -20,13 +20,13 @@ public class ComposePathEffect extends PathEffect { /** * Construct a PathEffect whose effect is to apply first the inner effect - * and the the outer pathEffect (e.g. outer(inner(path))). + * and the outer pathEffect (e.g. outer(inner(path))). */ public ComposePathEffect(PathEffect outerpe, PathEffect innerpe) { native_instance = nativeCreate(outerpe.native_instance, innerpe.native_instance); } - + private static native long nativeCreate(long nativeOuterpe, long nativeInnerpe); } diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java index f50de1665453..88f0e8ef8a94 100644 --- a/graphics/java/android/graphics/FontFamily.java +++ b/graphics/java/android/graphics/FontFamily.java @@ -45,15 +45,17 @@ public class FontFamily { private static String TAG = "FontFamily"; - private static final NativeAllocationRegistry sBuilderRegistry = - NativeAllocationRegistry.createMalloced( - FontFamily.class.getClassLoader(), nGetBuilderReleaseFunc()); - private @Nullable Runnable mNativeBuilderCleaner; - private static final NativeAllocationRegistry sFamilyRegistry = - NativeAllocationRegistry.createMalloced( - FontFamily.class.getClassLoader(), nGetFamilyReleaseFunc()); + private static class NoImagePreloadHolder { + private static final NativeAllocationRegistry sBuilderRegistry = + NativeAllocationRegistry.createMalloced( + FontFamily.class.getClassLoader(), nGetBuilderReleaseFunc()); + + private static final NativeAllocationRegistry sFamilyRegistry = + NativeAllocationRegistry.createMalloced( + FontFamily.class.getClassLoader(), nGetFamilyReleaseFunc()); + } /** * @hide @@ -74,7 +76,8 @@ public class FontFamily { publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.") public FontFamily() { mBuilderPtr = nInitBuilder(null, 0); - mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr); + mNativeBuilderCleaner = NoImagePreloadHolder.sBuilderRegistry.registerNativeAllocation(this, + mBuilderPtr); } /** @@ -92,7 +95,8 @@ public class FontFamily { langsString = TextUtils.join(",", langs); } mBuilderPtr = nInitBuilder(langsString, variant); - mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr); + mNativeBuilderCleaner = NoImagePreloadHolder.sBuilderRegistry.registerNativeAllocation(this, + mBuilderPtr); } /** @@ -113,7 +117,7 @@ public class FontFamily { mNativeBuilderCleaner.run(); mBuilderPtr = 0; if (mNativePtr != 0) { - sFamilyRegistry.registerNativeAllocation(this, mNativePtr); + NoImagePreloadHolder.sFamilyRegistry.registerNativeAllocation(this, mNativePtr); } return mNativePtr != 0; } @@ -215,7 +219,7 @@ public class FontFamily { @CriticalNative private static native long nGetFamilyReleaseFunc(); - // By passing -1 to weigth argument, the weight value is resolved by OS/2 table in the font. + // By passing -1 to weight argument, the weight value is resolved by OS/2 table in the font. // By passing -1 to italic argument, the italic value is resolved by OS/2 table in the font. private static native boolean nAddFont(long builderPtr, ByteBuffer font, int ttcIndex, int weight, int isItalic); diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java index 17c2dd94cd36..13c4a94cb9b6 100644 --- a/graphics/java/android/graphics/FontListParser.java +++ b/graphics/java/android/graphics/FontListParser.java @@ -127,7 +127,7 @@ public class FontListParser { parser.setInput(is, null); parser.nextTag(); return readFamilies(parser, systemFontDir, oemCustomization, updatableFontMap, - lastModifiedDate, configVersion, false /* filter out the non-exising files */); + lastModifiedDate, configVersion, false /* filter out the non-existing files */); } } @@ -254,7 +254,7 @@ public class FontListParser { * @param parser An XML parser. * @param fontDir a font directory name. * @param updatableFontMap a updated font file map. - * @param allowNonExistingFile true to allow font file that doesn't exists + * @param allowNonExistingFile true to allow font file that doesn't exist. * @return a FontFamily instance. null if no font files are available in this FontFamily. */ public static @Nullable FontConfig.FontFamily readFamily(XmlPullParser parser, String fontDir, diff --git a/graphics/java/android/graphics/FrameInfo.java b/graphics/java/android/graphics/FrameInfo.java index b3615ff60bce..8f1282897780 100644 --- a/graphics/java/android/graphics/FrameInfo.java +++ b/graphics/java/android/graphics/FrameInfo.java @@ -24,7 +24,7 @@ import java.lang.annotation.RetentionPolicy; /** * Class that contains all the timing information for the current frame. This * is used in conjunction with the hardware renderer to provide - * continous-monitoring jank events + * continuous-monitoring jank events * * All times in nanoseconds from CLOCK_MONOTONIC/System.nanoTime() * diff --git a/graphics/java/android/graphics/Interpolator.java b/graphics/java/android/graphics/Interpolator.java index 994fb2dc31bc..28f296d3621d 100644 --- a/graphics/java/android/graphics/Interpolator.java +++ b/graphics/java/android/graphics/Interpolator.java @@ -28,13 +28,13 @@ public class Interpolator { mFrameCount = 2; native_instance = nativeConstructor(valueCount, 2); } - + public Interpolator(int valueCount, int frameCount) { mValueCount = valueCount; mFrameCount = frameCount; native_instance = nativeConstructor(valueCount, frameCount); } - + /** * Reset the Interpolator to have the specified number of values and an * implicit keyFrame count of 2 (just a start and end). After this call the @@ -43,7 +43,7 @@ public class Interpolator { public void reset(int valueCount) { reset(valueCount, 2); } - + /** * Reset the Interpolator to have the specified number of values and * keyFrames. After this call the values for each keyFrame must be assigned @@ -54,20 +54,20 @@ public class Interpolator { mFrameCount = frameCount; nativeReset(native_instance, valueCount, frameCount); } - + public final int getKeyFrameCount() { return mFrameCount; } - + public final int getValueCount() { return mValueCount; } - + /** * Assign the keyFrame (specified by index) a time value and an array of key - * values (with an implicity blend array of [0, 0, 1, 1] giving linear + * values (with an implicitly blend array of [0, 0, 1, 1] giving linear * transition to the next set of key values). - * + * * @param index The index of the key frame to assign * @param msec The time (in mililiseconds) for this key frame. Based on the * SystemClock.uptimeMillis() clock @@ -80,7 +80,7 @@ public class Interpolator { /** * Assign the keyFrame (specified by index) a time value and an array of key * values and blend array. - * + * * @param index The index of the key frame to assign * @param msec The time (in mililiseconds) for this key frame. Based on the * SystemClock.uptimeMillis() clock @@ -99,7 +99,7 @@ public class Interpolator { } nativeSetKeyFrame(native_instance, index, msec, values, blend); } - + /** * Set a repeat count (which may be fractional) for the interpolator, and * whether the interpolator should mirror its repeats. The default settings @@ -110,7 +110,7 @@ public class Interpolator { nativeSetRepeatMirror(native_instance, repeatCount, mirror); } } - + public enum Result { NORMAL, FREEZE_START, @@ -130,7 +130,7 @@ public class Interpolator { * return whether the specified time was within the range of key times * (NORMAL), was before the first key time (FREEZE_START) or after the last * key time (FREEZE_END). In any event, computed values are always returned. - * + * * @param msec The time (in milliseconds) used to sample into the * Interpolator. Based on the SystemClock.uptimeMillis() clock * @param values Where to write the computed values (may be NULL). @@ -146,13 +146,13 @@ public class Interpolator { default: return Result.FREEZE_END; } } - + @Override protected void finalize() throws Throwable { nativeDestructor(native_instance); native_instance = 0; // Other finalizers can still call us. } - + private int mValueCount; private int mFrameCount; private long native_instance; diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java index 0aa6f1282c1a..fe73a1a70b9c 100644 --- a/graphics/java/android/graphics/LightingColorFilter.java +++ b/graphics/java/android/graphics/LightingColorFilter.java @@ -16,8 +16,8 @@ // This file was generated from the C++ include file: SkColorFilter.h // Any changes made to this file will be discarded by the build. -// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, -// or one of the auxilary file specifications in device/tools/gluemaker. +// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, +// or one of the auxiliary file specifications in device/tools/gluemaker. package android.graphics; diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java index 56d912b1eada..087937144b97 100644 --- a/graphics/java/android/graphics/LinearGradient.java +++ b/graphics/java/android/graphics/LinearGradient.java @@ -63,7 +63,7 @@ public class LinearGradient extends Shader { * @param colors The sRGB colors to be distributed along the gradient line * @param positions May be null. The relative positions [0..1] of * each corresponding color in the colors array. If this is null, - * the the colors are distributed evenly along the gradient line. + * the colors are distributed evenly along the gradient line. * @param tile The Shader tiling mode */ public LinearGradient(float x0, float y0, float x1, float y1, @NonNull @ColorInt int[] colors, @@ -82,7 +82,7 @@ public class LinearGradient extends Shader { * @param colors The colors to be distributed along the gradient line * @param positions May be null. The relative positions [0..1] of * each corresponding color in the colors array. If this is null, - * the the colors are distributed evenly along the gradient line. + * the colors are distributed evenly along the gradient line. * @param tile The Shader tiling mode * * @throws IllegalArgumentException if there are less than two colors, the colors do diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java index fbb690c0b012..748e9dd98e10 100644 --- a/graphics/java/android/graphics/Matrix.java +++ b/graphics/java/android/graphics/Matrix.java @@ -232,7 +232,7 @@ public class Matrix { private static class NoImagePreloadHolder { public static final NativeAllocationRegistry sRegistry = NativeAllocationRegistry.createMalloced( - Matrix.class.getClassLoader(), nGetNativeFinalizerWrapper()); + Matrix.class.getClassLoader(), ExtraNatives.nGetNativeFinalizer()); } private final long native_instance; @@ -241,7 +241,7 @@ public class Matrix { * Create an identity matrix */ public Matrix() { - native_instance = nCreateWrapper(0); + native_instance = ExtraNatives.nCreate(0); NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance); } @@ -251,7 +251,7 @@ public class Matrix { * @param src The matrix to copy into this matrix */ public Matrix(Matrix src) { - native_instance = nCreateWrapper(src != null ? src.native_instance : 0); + native_instance = ExtraNatives.nCreate(src != null ? src.native_instance : 0); NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, native_instance); } @@ -562,7 +562,7 @@ public class Matrix { /** * Set the matrix to the scale and translate values that map the source rectangle to the - * destination rectangle, returning true if the the result can be represented. + * destination rectangle, returning true if the result can be represented. * * @param src the source rectangle to map from. * @param dst the destination rectangle to map to. @@ -849,40 +849,6 @@ public class Matrix { return native_instance; } - /** - * Wrapper method we use to switch to ExtraNatives.nCreate(src) only on Ravenwood. - * - * @see ExtraNatives - */ - @android.ravenwood.annotation.RavenwoodReplace - private static long nCreateWrapper(long src) { - return nCreate(src); - } - - private static long nCreateWrapper$ravenwood(long src) { - return ExtraNatives.nCreate(src); - } - - /** - * Wrapper method we use to switch to ExtraNatives.nGetNativeFinalizer(src) only on Ravenwood. - * - * @see ExtraNatives - */ - @android.ravenwood.annotation.RavenwoodReplace - private static long nGetNativeFinalizerWrapper() { - return nGetNativeFinalizer(); - } - - private static long nGetNativeFinalizerWrapper$ravenwood() { - return ExtraNatives.nGetNativeFinalizer(); - } - - // ------------------ Regular JNI ------------------------ - - private static native long nCreate(long nSrc_or_zero); - private static native long nGetNativeFinalizer(); - - // ------------------ Fast JNI ------------------------ @FastNative @@ -982,14 +948,6 @@ public class Matrix { * There are two methods that are called by the static initializers (either directly or * indirectly) in this class, namely nCreate() and nGetNativeFinalizer(). On Ravenwood * these methods can't be on the Matrix class itself, so we use a nested class to host them. - * - * We still keep the original nCreate() method and call it on non-ravenwood environment, - * in order to avoid problems in downstream (such as Android Studio). - * - * @see #nCreateWrapper(long) - * @see #nGetNativeFinalizerWrapper() - * - * TODO(b/337110712) Clean it up somehow. (remove the original nCreate() and unify the code?) */ private static class ExtraNatives { static native long nCreate(long nSrc_or_zero); diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java index a4bce9eb5e88..6be8332e784b 100644 --- a/graphics/java/android/graphics/Mesh.java +++ b/graphics/java/android/graphics/Mesh.java @@ -271,7 +271,7 @@ public class Mesh { * not have a uniform with that name or if the uniform is declared with a type other than int * or int[1] then an IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param value value corresponding to the int uniform with the given name. */ public void setIntUniform(@NonNull String uniformName, int value) { @@ -283,7 +283,7 @@ public class Mesh { * not have a uniform with that name or if the uniform is declared with a type other than ivec2 * or int[2] then an IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. * @param value2 second value corresponding to the int uniform with the given name. */ @@ -296,7 +296,7 @@ public class Mesh { * not have a uniform with that name or if the uniform is declared with a type other than ivec3 * or int[3] then an IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. * @param value2 second value corresponding to the int uniform with the given name. * @param value3 third value corresponding to the int uniform with the given name. @@ -310,7 +310,7 @@ public class Mesh { * not have a uniform with that name or if the uniform is declared with a type other than ivec4 * or int[4] then an IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param value1 first value corresponding to the int uniform with the given name. * @param value2 second value corresponding to the int uniform with the given name. * @param value3 third value corresponding to the int uniform with the given name. @@ -327,7 +327,7 @@ public class Mesh { * int (for N=1), ivecN, or int[N], where N is the length of the values param, then an * IllegalArgumentException is thrown. * - * @param uniformName name matching the int uniform delcared in the shader program. + * @param uniformName name matching the int uniform declared in the shader program. * @param values int values corresponding to the vec4 int uniform with the given name. */ public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) { diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java index af2095713ec2..382269f74366 100644 --- a/graphics/java/android/graphics/NinePatch.java +++ b/graphics/java/android/graphics/NinePatch.java @@ -22,12 +22,12 @@ import android.compat.annotation.UnsupportedAppUsage; * The NinePatch class permits drawing a bitmap in nine or more sections. * Essentially, it allows the creation of custom graphics that will scale the * way that you define, when content added within the image exceeds the normal - * bounds of the graphic. For a thorough explanation of a NinePatch image, - * read the discussion in the + * bounds of the graphic. For a thorough explanation of a NinePatch image, + * read the discussion in the * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">2D * Graphics</a> document. * <p> - * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a> + * The <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-Patch</a> * tool offers an extremely handy way to create your NinePatch images, * using a WYSIWYG graphics editor. * </p> @@ -104,7 +104,7 @@ public class NinePatch { this(bitmap, chunk, null); } - /** + /** * Create a drawable projection from a bitmap to nine patches. * * @param bitmap The bitmap describing the patches. @@ -122,7 +122,7 @@ public class NinePatch { protected void finalize() throws Throwable { try { if (mNativeChunk != 0) { - // only attempt to destroy correctly initilized chunks + // only attempt to destroy correctly initialized chunks nativeFinalize(mNativeChunk); mNativeChunk = 0; } @@ -169,8 +169,8 @@ public class NinePatch { public Bitmap getBitmap() { return mBitmap; } - - /** + + /** * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}. * * @param canvas A container for the current matrix and clip used to draw the NinePatch. @@ -180,7 +180,7 @@ public class NinePatch { canvas.drawPatch(this, location, mPaint); } - /** + /** * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}. * * @param canvas A container for the current matrix and clip used to draw the NinePatch. @@ -190,7 +190,7 @@ public class NinePatch { canvas.drawPatch(this, location, mPaint); } - /** + /** * Draws the NinePatch. This method will ignore the paint returned * by {@link #getPaint()} and use the specified paint instead. * diff --git a/graphics/java/android/graphics/PathMeasure.java b/graphics/java/android/graphics/PathMeasure.java index 5500c5217de4..2c6cfa5c2e3d 100644 --- a/graphics/java/android/graphics/PathMeasure.java +++ b/graphics/java/android/graphics/PathMeasure.java @@ -25,14 +25,14 @@ public class PathMeasure { * setPath. * * Note that once a path is associated with the measure object, it is - * undefined if the path is subsequently modified and the the measure object + * undefined if the path is subsequently modified and the measure object * is used. If the path is modified, you must call setPath with the path. */ public PathMeasure() { mPath = null; native_instance = native_create(0, false); } - + /** * Create a PathMeasure object associated with the specified path object * (already created and specified). The measure object can now return the @@ -40,7 +40,7 @@ public class PathMeasure { * path. * * Note that once a path is associated with the measure object, it is - * undefined if the path is subsequently modified and the the measure object + * undefined if the path is subsequently modified and the measure object * is used. If the path is modified, you must call setPath with the path. * * @param path The path that will be measured by this object @@ -121,7 +121,7 @@ public class PathMeasure { * such as <code>dst.rLineTo(0, 0)</code>.</p> */ public boolean getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo) { - // Skia used to enforce this as part of it's API, but has since relaxed that restriction + // Skia used to enforce this as part of its API, but has since relaxed that restriction // so to maintain consistency in our API we enforce the preconditions here. float length = getLength(); if (startD < 0) { diff --git a/graphics/java/android/graphics/Rasterizer.java b/graphics/java/android/graphics/Rasterizer.java index 29d82fa1d98b..575095426563 100644 --- a/graphics/java/android/graphics/Rasterizer.java +++ b/graphics/java/android/graphics/Rasterizer.java @@ -16,8 +16,8 @@ // This file was generated from the C++ include file: SkRasterizer.h // Any changes made to this file will be discarded by the build. -// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, -// or one of the auxilary file specifications in device/tools/gluemaker. +// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, +// or one of the auxiliary file specifications in device/tools/gluemaker. package android.graphics; 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/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java index 411a10b85bd2..5211e3a75d09 100644 --- a/graphics/java/android/graphics/Rect.java +++ b/graphics/java/android/graphics/Rect.java @@ -165,7 +165,7 @@ public final class Rect implements Parcelable { public String toShortString() { return toShortString(new StringBuilder(32)); } - + /** * Return a string representation of the rectangle in a compact form. * @hide @@ -184,7 +184,7 @@ public final class Rect implements Parcelable { * * <p>You can later recover the Rect from this string through * {@link #unflattenFromString(String)}. - * + * * @return Returns a new String of the form "left top right bottom" */ @NonNull @@ -314,7 +314,7 @@ public final class Rect implements Parcelable { public final int height() { return bottom - top; } - + /** * @return the horizontal center of the rectangle. If the computed value * is fractional, this method returns the largest integer that is @@ -323,7 +323,7 @@ public final class Rect implements Parcelable { public final int centerX() { return (left + right) >> 1; } - + /** * @return the vertical center of the rectangle. If the computed value * is fractional, this method returns the largest integer that is @@ -332,14 +332,14 @@ public final class Rect implements Parcelable { public final int centerY() { return (top + bottom) >> 1; } - + /** * @return the exact horizontal center of the rectangle as a float. */ public final float exactCenterX() { return (left + right) * 0.5f; } - + /** * @return the exact vertical center of the rectangle as a float. */ @@ -493,7 +493,7 @@ public final class Rect implements Parcelable { * @param top The top of the rectangle being tested for containment * @param right The right side of the rectangle being tested for containment * @param bottom The bottom of the rectangle being tested for containment - * @return true iff the the 4 specified sides of a rectangle are inside or + * @return true iff the 4 specified sides of a rectangle are inside or * equal to this rectangle */ public boolean contains(int left, int top, int right, int bottom) { @@ -548,7 +548,7 @@ public final class Rect implements Parcelable { } return false; } - + /** * If the specified rectangle intersects this rectangle, return true and set * this rectangle to that intersection, otherwise return false and do not @@ -670,7 +670,7 @@ public final class Rect implements Parcelable { public void union(@NonNull Rect r) { union(r.left, r.top, r.right, r.bottom); } - + /** * Update this Rect to enclose itself and the [x,y] coordinate. There is no * check to see that this rectangle is non-empty. diff --git a/graphics/java/android/graphics/RectF.java b/graphics/java/android/graphics/RectF.java index ff50a0c55e6c..5b9b7641111b 100644 --- a/graphics/java/android/graphics/RectF.java +++ b/graphics/java/android/graphics/RectF.java @@ -38,7 +38,7 @@ public class RectF implements Parcelable { public float top; public float right; public float bottom; - + /** * Create a new empty RectF. All coordinates are initialized to 0. */ @@ -78,7 +78,7 @@ public class RectF implements Parcelable { bottom = r.bottom; } } - + public RectF(@Nullable Rect r) { if (r == null) { left = top = right = bottom = 0.0f; @@ -121,7 +121,7 @@ public class RectF implements Parcelable { public String toShortString() { return toShortString(new StringBuilder(32)); } - + /** * Return a string representation of the rectangle in a compact form. * @hide @@ -134,7 +134,7 @@ public class RectF implements Parcelable { sb.append(','); sb.append(bottom); sb.append(']'); return sb.toString(); } - + /** * Print short representation to given writer. * @hide @@ -183,14 +183,14 @@ public class RectF implements Parcelable { public final float centerY() { return (top + bottom) * 0.5f; } - + /** * Set the rectangle to (0,0,0,0) */ public void setEmpty() { left = right = top = bottom = 0; } - + /** * Set the rectangle's coordinates to the specified values. Note: no range * checking is performed, so it is up to the caller to ensure that @@ -220,7 +220,7 @@ public class RectF implements Parcelable { this.right = src.right; this.bottom = src.bottom; } - + /** * Copy the coordinates from src into this rectangle. * @@ -261,7 +261,7 @@ public class RectF implements Parcelable { left = newLeft; top = newTop; } - + /** * Inset the rectangle by (dx,dy). If dx is positive, then the sides are * moved inwards, making the rectangle narrower. If dx is negative, then the @@ -293,7 +293,7 @@ public class RectF implements Parcelable { return left < right && top < bottom // check for empty first && x >= left && x < right && y >= top && y < bottom; } - + /** * Returns true iff the 4 specified sides of a rectangle are inside or equal * to this rectangle. i.e. is this rectangle a superset of the specified @@ -303,7 +303,7 @@ public class RectF implements Parcelable { * @param top The top of the rectangle being tested for containment * @param right The right side of the rectangle being tested for containment * @param bottom The bottom of the rectangle being tested for containment - * @return true iff the the 4 specified sides of a rectangle are inside or + * @return true iff the 4 specified sides of a rectangle are inside or * equal to this rectangle */ public boolean contains(float left, float top, float right, float bottom) { @@ -313,7 +313,7 @@ public class RectF implements Parcelable { && this.left <= left && this.top <= top && this.right >= right && this.bottom >= bottom; } - + /** * Returns true iff the specified rectangle r is inside or equal to this * rectangle. An empty rectangle never contains another rectangle. @@ -329,7 +329,7 @@ public class RectF implements Parcelable { && left <= r.left && top <= r.top && right >= r.right && bottom >= r.bottom; } - + /** * If the rectangle specified by left,top,right,bottom intersects this * rectangle, return true and set this rectangle to that intersection, @@ -367,7 +367,7 @@ public class RectF implements Parcelable { } return false; } - + /** * If the specified rectangle intersects this rectangle, return true and set * this rectangle to that intersection, otherwise return false and do not @@ -382,7 +382,7 @@ public class RectF implements Parcelable { public boolean intersect(@NonNull RectF r) { return intersect(r.left, r.top, r.right, r.bottom); } - + /** * If rectangles a and b intersect, return true and set this rectangle to * that intersection, otherwise return false and do not change this @@ -406,7 +406,7 @@ public class RectF implements Parcelable { } return false; } - + /** * Returns true if this rectangle intersects the specified rectangle. * In no event is this rectangle modified. No check is performed to see @@ -426,7 +426,7 @@ public class RectF implements Parcelable { return this.left < right && left < this.right && this.top < bottom && top < this.bottom; } - + /** * Returns true iff the two specified rectangles intersect. In no event are * either of the rectangles modified. To record the intersection, @@ -441,7 +441,7 @@ public class RectF implements Parcelable { return a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom; } - + /** * Set the dst integer Rect by rounding this rectangle's coordinates * to their nearest integer values. @@ -489,7 +489,7 @@ public class RectF implements Parcelable { } } } - + /** * Update this Rect to enclose itself and the specified rectangle. If the * specified rectangle is empty, nothing is done. If this rectangle is empty @@ -500,7 +500,7 @@ public class RectF implements Parcelable { public void union(@NonNull RectF r) { union(r.left, r.top, r.right, r.bottom); } - + /** * Update this Rect to enclose itself and the [x,y] coordinate. There is no * check to see that this rectangle is non-empty. @@ -520,7 +520,7 @@ public class RectF implements Parcelable { bottom = y; } } - + /** * Swap top/bottom or left/right if there are flipped (i.e. left > right * and/or top > bottom). This can be called if @@ -548,7 +548,7 @@ public class RectF implements Parcelable { public int describeContents() { return 0; } - + /** * Write this rectangle to the specified parcel. To restore a rectangle from * a parcel, use readFromParcel() @@ -561,7 +561,7 @@ public class RectF implements Parcelable { out.writeFloat(right); out.writeFloat(bottom); } - + public static final @android.annotation.NonNull Parcelable.Creator<RectF> CREATOR = new Parcelable.Creator<RectF>() { /** * Return a new rectangle from the data in the specified parcel. @@ -572,7 +572,7 @@ public class RectF implements Parcelable { r.readFromParcel(in); return r; } - + /** * Return an array of rectangles of the specified size. */ @@ -581,7 +581,7 @@ public class RectF implements Parcelable { return new RectF[size]; } }; - + /** * Set the rectangle's coordinates from the data stored in the specified * parcel. To write a rectangle to a parcel, call writeToParcel(). diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 27325694073c..0650b7817729 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -765,12 +765,12 @@ public final class RenderNode { * Default value is false. See * {@link #setProjectBackwards(boolean)} for a description of what this entails. * - * @param shouldRecieve True if this RenderNode is a projection receiver, false otherwise. + * @param shouldReceive True if this RenderNode is a projection receiver, false otherwise. * Default is false. * @return True if the value changed, false if the new value was the same as the previous value. */ - public boolean setProjectionReceiver(boolean shouldRecieve) { - return nSetProjectionReceiver(mNativeRenderNode, shouldRecieve); + public boolean setProjectionReceiver(boolean shouldReceive) { + return nSetProjectionReceiver(mNativeRenderNode, shouldReceive); } /** @@ -1799,7 +1799,7 @@ public final class RenderNode { private static native boolean nSetProjectBackwards(long renderNode, boolean shouldProject); @CriticalNative - private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldRecieve); + private static native boolean nSetProjectionReceiver(long renderNode, boolean shouldReceive); @CriticalNative private static native boolean nSetOutlineRoundRect(long renderNode, int left, int top, diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java index 50b167ef9f46..3256f31bdc93 100644 --- a/graphics/java/android/graphics/SurfaceTexture.java +++ b/graphics/java/android/graphics/SurfaceTexture.java @@ -318,7 +318,7 @@ public class SurfaceTexture { } /** - * Releases the the texture content. This is needed in single buffered mode to allow the image + * Releases the texture content. This is needed in single buffered mode to allow the image * content producer to take ownership of the image buffer. * <p> * For more information see {@link #SurfaceTexture(int, boolean)}. @@ -431,7 +431,7 @@ public class SurfaceTexture { * error. * <p> * Note that while calling this method causes all the buffers to be freed - * from the perspective of the the SurfaceTexture, if there are additional + * from the perspective of the SurfaceTexture, if there are additional * references on the buffers (e.g. if a buffer is referenced by a client or * by OpenGL ES as a texture) then those buffer will remain allocated. * <p> diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java index 4c4e8fa9c088..fd788167a0d8 100644 --- a/graphics/java/android/graphics/Typeface.java +++ b/graphics/java/android/graphics/Typeface.java @@ -600,7 +600,7 @@ public class Typeface { * {@link #setWeight} and {@link #setItalic}. * * If {@link #setWeight} is not called, the fallback family keeps the default weight. - * Similary, if {@link #setItalic} is not called, the fallback family keeps the default + * Similarly, if {@link #setItalic} is not called, the fallback family keeps the default * italic information. For example, calling {@code builder.setFallback("sans-serif-light")} * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in * terms of fallback. The default weight and italic information are overridden by calling @@ -794,7 +794,7 @@ public class Typeface { /** * Returns the maximum capacity of custom fallback families. * - * This includes the the first font family passed to the constructor. + * This includes the first font family passed to the constructor. * It is guaranteed that the value will be greater than or equal to 64. * * @return the maximum number of font families for the custom fallback @@ -816,7 +816,7 @@ public class Typeface { /** * Sets a system fallback by name. * - * You can specify generic font familiy names or OEM specific family names. If the system + * You can specify generic font family names or OEM specific family names. If the system * don't have a specified fallback, the default fallback is used instead. * For more information about generic font families, see <a * href="https://www.w3.org/TR/css-fonts-4/#generic-font-families">CSS specification</a> diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java index 81769e2e21bf..6bb22a12280e 100644 --- a/graphics/java/android/graphics/Xfermode.java +++ b/graphics/java/android/graphics/Xfermode.java @@ -16,8 +16,8 @@ // This file was generated from the C++ include file: SkXfermode.h // Any changes made to this file will be discarded by the build. -// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, -// or one of the auxilary file specifications in device/tools/gluemaker. +// To change this file, either edit the include, or device/tools/gluemaker/main.cpp, +// or one of the auxiliary file specifications in device/tools/gluemaker. package android.graphics; @@ -28,7 +28,7 @@ import android.os.Build; * Xfermode is the base class for objects that are called to implement custom * "transfer-modes" in the drawing pipeline. The static function Create(Modes) * can be called to return an instance of any of the predefined subclasses as - * specified in the Modes enum. When an Xfermode is assigned to an Paint, then + * specified in the Modes enum. When an Xfermode is assigned to a Paint, then * objects drawn with that paint have the xfermode applied. */ public class Xfermode { diff --git a/graphics/java/android/graphics/YuvImage.java b/graphics/java/android/graphics/YuvImage.java index ce35b55d526f..b0c7f202f23a 100644 --- a/graphics/java/android/graphics/YuvImage.java +++ b/graphics/java/android/graphics/YuvImage.java @@ -63,7 +63,7 @@ public class YuvImage { private int mWidth; /** - * The height of the the image. + * The height of the image. */ private int mHeight; diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java index 688425a77ab5..7ee7d6beec78 100644 --- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java +++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java @@ -98,7 +98,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback * extra content to reveal within the clip path when performing affine transformations on the * layers. * - * Each layers will reserve 25% of it's width and height. + * Each layers will reserve 25% of its width and height. * * As a result, the view port of the layers is smaller than their intrinsic width and height. */ diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 4972e928dd22..7f2feac2278d 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -839,7 +839,7 @@ public abstract class Drawable { } /** - * Describes the current state, as a union of primitve states, such as + * Describes the current state, as a union of primitive states, such as * {@link android.R.attr#state_focused}, * {@link android.R.attr#state_selected}, etc. * Some drawables may modify their imagery based on the selected state. diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 166a795e1661..29d033e64aea 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -936,7 +936,7 @@ public class GradientDrawable extends Drawable { } /** - * Retrn the inner radius of the ring + * Return the inner radius of the ring * * @see #setInnerRadius(int) * @attr ref android.R.styleable#GradientDrawable_innerRadius diff --git a/graphics/java/android/graphics/drawable/shapes/PathShape.java b/graphics/java/android/graphics/drawable/shapes/PathShape.java index 393fdee87bb8..299f6d5cddfa 100644 --- a/graphics/java/android/graphics/drawable/shapes/PathShape.java +++ b/graphics/java/android/graphics/drawable/shapes/PathShape.java @@ -93,7 +93,7 @@ public class PathShape extends Shape { && Float.compare(pathShape.mStdHeight, mStdHeight) == 0 && Float.compare(pathShape.mScaleX, mScaleX) == 0 && Float.compare(pathShape.mScaleY, mScaleY) == 0 - // Path does not have equals implementation but incase it gains one, use it here + // Path does not have equals implementation but in case it gains one, use it here && Objects.equals(mPath, pathShape.mPath); } diff --git a/graphics/java/android/graphics/fonts/Font.java b/graphics/java/android/graphics/fonts/Font.java index 28cc05162cbb..2893177aafc5 100644 --- a/graphics/java/android/graphics/fonts/Font.java +++ b/graphics/java/android/graphics/fonts/Font.java @@ -61,13 +61,15 @@ public final class Font { private static final int STYLE_ITALIC = 1; private static final int STYLE_NORMAL = 0; - private static final NativeAllocationRegistry BUFFER_REGISTRY = - NativeAllocationRegistry.createMalloced( - ByteBuffer.class.getClassLoader(), nGetReleaseNativeFont()); - - private static final NativeAllocationRegistry FONT_REGISTRY = - NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(), - nGetReleaseNativeFont()); + private static class NoImagePreloadHolder { + private static final NativeAllocationRegistry BUFFER_REGISTRY = + NativeAllocationRegistry.createMalloced( + ByteBuffer.class.getClassLoader(), nGetReleaseNativeFont()); + + private static final NativeAllocationRegistry FONT_REGISTRY = + NativeAllocationRegistry.createMalloced(Font.class.getClassLoader(), + nGetReleaseNativeFont()); + } /** * A builder class for creating new Font. @@ -530,7 +532,7 @@ public final class Font { public Font(long nativePtr) { mNativePtr = nativePtr; - FONT_REGISTRY.registerNativeAllocation(this, mNativePtr); + NoImagePreloadHolder.FONT_REGISTRY.registerNativeAllocation(this, mNativePtr); } /** @@ -551,7 +553,7 @@ public final class Font { ByteBuffer fromNative = nNewByteBuffer(mNativePtr); // Bind ByteBuffer's lifecycle with underlying font object. - BUFFER_REGISTRY.registerNativeAllocation(fromNative, ref); + NoImagePreloadHolder.BUFFER_REGISTRY.registerNativeAllocation(fromNative, ref); // JNI NewDirectBuffer creates writable ByteBuffer even if it is mmaped readonly. mBuffer = fromNative.asReadOnlyBuffer(); @@ -787,7 +789,7 @@ public final class Font { return false; } - // ByteBuffer#equals compares all bytes which is not performant for e.g HashMap. Since + // ByteBuffer#equals compares all bytes which is not performant for e.g. HashMap. Since // underlying native font object holds buffer address, check if this buffer points exactly // the same address as a shortcut of equality. For being compatible with of API30 or before, // check buffer position even if the buffer points the same address. diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index 199e9293ad65..5a7b0bbca399 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -25,6 +25,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.text.FontConfig; import android.util.SparseIntArray; @@ -73,9 +74,11 @@ public final class FontFamily { * A builder class for creating new FontFamily. */ public static final class Builder { - private static final NativeAllocationRegistry sFamilyRegistory = - NativeAllocationRegistry.createMalloced(FontFamily.class.getClassLoader(), - nGetReleaseNativeFamily()); + private static class NoImagePreloadHolder { + private static final NativeAllocationRegistry sFamilyRegistry = + NativeAllocationRegistry.createMalloced(FontFamily.class.getClassLoader(), + nGetReleaseNativeFamily()); + } private final ArrayList<Font> mFonts = new ArrayList<>(); // Most FontFamily only has regular, bold, italic, bold-italic. Thus 4 should be good for @@ -149,6 +152,7 @@ public final class FontFamily { * @return A variable font family. null if a variable font cannot be built from the given * fonts. */ + @SuppressLint("BuilderSetStyle") @FlaggedApi(FLAG_NEW_FONTS_FALLBACK_XML) public @Nullable FontFamily buildVariableFamily() { int variableFamilyType = analyzeAndResolveVariableType(mFonts); @@ -183,7 +187,7 @@ public final class FontFamily { final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback, isDefaultFallback, variableFamilyType); final FontFamily family = new FontFamily(ptr); - sFamilyRegistory.registerNativeAllocation(family, ptr); + NoImagePreloadHolder.sFamilyRegistry.registerNativeAllocation(family, ptr); return family; } diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index ff38282255f2..abcafb666576 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -34,7 +34,7 @@ import java.util.Set; */ public class FontFileUtil { - private FontFileUtil() {} // Do not instanciate + private FontFileUtil() {} // Do not instantiate /** * Unpack the weight value from packed integer. @@ -87,7 +87,7 @@ public class FontFileUtil { } if (weight != -1 && italic != -1) { - // Both weight/italic style are specifeid by variation settings. + // Both weight/italic style are specified by variation settings. // No need to look into OS/2 table. // TODO: Good to look HVAR table to check if this font supports wght/ital axes. return pack(weight, italic == 1); diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index a90961eb6d2e..f727f5b076a1 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -216,7 +216,7 @@ public final class SystemFonts { } else if (defaultFamily != null) { familyListSet.familyList.add(defaultFamily); } else { - // There is no valid for for default fallback. Ignore. + // There is no valid for default fallback. Ignore. } } } diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index 7d55928aa656..5a1086cef407 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -23,6 +23,7 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.ActivityThread; import android.os.Build; import android.os.LocaleList; @@ -314,6 +315,7 @@ public final class LineBreakConfig implements Parcelable { * @param config an override line break config * @return This {@code Builder}. */ + @SuppressLint("BuilderSetStyle") @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public @NonNull Builder merge(@NonNull LineBreakConfig config) { if (config.mLineBreakStyle != LINE_BREAK_STYLE_UNSPECIFIED) { diff --git a/graphics/java/android/graphics/text/LineBreaker.java b/graphics/java/android/graphics/text/LineBreaker.java index 0e9f29db8cb5..94de066c9182 100644 --- a/graphics/java/android/graphics/text/LineBreaker.java +++ b/graphics/java/android/graphics/text/LineBreaker.java @@ -18,6 +18,8 @@ package android.graphics.text; import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH; import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION; +import static com.android.text.flags.Flags.FLAG_MISSING_GETTER_APIS; + import android.annotation.FlaggedApi; import android.annotation.FloatRange; @@ -376,8 +378,8 @@ public class LineBreaker { * @see LineBreaker#computeLineBreaks */ public static class Result { - // Following two contstant must be synced with minikin's line breaker. - // TODO(nona): Remove these constatns by introducing native methods. + // Following two constants must be synced with minikin's line breaker. + // TODO(nona): Remove these constants by introducing native methods. private static final int TAB_MASK = 0x20000000; private static final int HYPHEN_MASK = 0xFF; private static final int START_HYPHEN_MASK = 0x18; // 0b11000 @@ -480,12 +482,20 @@ public class LineBreaker { } } - private static final NativeAllocationRegistry sRegistry = - NativeAllocationRegistry.createMalloced( - LineBreaker.class.getClassLoader(), nGetReleaseFunc()); + private static class NoImagePreloadHolder { + private static final NativeAllocationRegistry sRegistry = + NativeAllocationRegistry.createMalloced( + LineBreaker.class.getClassLoader(), nGetReleaseFunc()); + } private final long mNativePtr; + private final @BreakStrategy int mBreakStrategy; + private final @HyphenationFrequency int mHyphenationFrequency; + private final @JustificationMode int mJustificationMode; + private final int[] mIndents; + private final boolean mUseBoundsForWidth; + /** * Use Builder instead. */ @@ -494,7 +504,68 @@ public class LineBreaker { @Nullable int[] indents, boolean useBoundsForWidth) { mNativePtr = nInit(breakStrategy, hyphenationFrequency, justify == JUSTIFICATION_MODE_INTER_WORD, indents, useBoundsForWidth); - sRegistry.registerNativeAllocation(this, mNativePtr); + NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativePtr); + + mBreakStrategy = breakStrategy; + mHyphenationFrequency = hyphenationFrequency; + mJustificationMode = justify; + mIndents = indents; + mUseBoundsForWidth = useBoundsForWidth; + } + + /** + * Returns the break strategy used for this line breaker. + * + * @return the break strategy used for this line breaker. + * @see Builder#setBreakStrategy(int) + */ + @FlaggedApi(FLAG_MISSING_GETTER_APIS) + public @BreakStrategy int getBreakStrategy() { + return mBreakStrategy; + } + + /** + * Returns the hyphenation frequency used for this line breaker. + * + * @return the hyphenation frequency used for this line breaker. + * @see Builder#setHyphenationFrequency(int) + */ + @FlaggedApi(FLAG_MISSING_GETTER_APIS) + public @HyphenationFrequency int getHyphenationFrequency() { + return mHyphenationFrequency; + } + + /** + * Returns the justification mode used for this line breaker. + * + * @return the justification mode used for this line breaker. + * @see Builder#setJustificationMode(int) + */ + @FlaggedApi(FLAG_MISSING_GETTER_APIS) + public @JustificationMode int getJustificationMode() { + return mJustificationMode; + } + + /** + * Returns the indents used for this line breaker. + * + * @return the indents used for this line breaker. + * @see Builder#setIndents(int[]) + */ + @FlaggedApi(FLAG_MISSING_GETTER_APIS) + public @Nullable int[] getIndents() { + return mIndents; + } + + /** + * Returns true if this line breaker uses bounds as width for line breaking. + * + * @return true if this line breaker uses bounds as width for line breaking. + * @see Builder#setUseBoundsForWidth(boolean) + */ + @FlaggedApi(FLAG_MISSING_GETTER_APIS) + public boolean getUseBoundsForWidth() { + return mUseBoundsForWidth; } /** diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index 7932e3334063..671eb6e514c5 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -46,9 +46,11 @@ import java.util.Objects; * @see TextRunShaper#shapeTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint) */ public final class PositionedGlyphs { - private static final NativeAllocationRegistry REGISTRY = - NativeAllocationRegistry.createMalloced( - Typeface.class.getClassLoader(), nReleaseFunc()); + private static class NoImagePreloadHolder { + private static final NativeAllocationRegistry REGISTRY = + NativeAllocationRegistry.createMalloced( + Typeface.class.getClassLoader(), nReleaseFunc()); + } private final long mLayoutPtr; private final float mXOffset; @@ -137,7 +139,7 @@ public final class PositionedGlyphs { * Returns the glyph ID used for drawing the glyph at the given index. * * @param index the glyph index - * @return An glyph ID of the font. + * @return A glyph ID of the font. */ @IntRange(from = 0) public int getGlyphId(@IntRange(from = 0) int index) { @@ -259,7 +261,7 @@ public final class PositionedGlyphs { mFonts.add(prevFont); } - REGISTRY.registerNativeAllocation(this, layoutPtr); + NoImagePreloadHolder.REGISTRY.registerNativeAllocation(this, layoutPtr); } @CriticalNative diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java deleted file mode 100644 index d1d7c145680f..000000000000 --- a/keystore/java/android/security/KeyStore.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.security; - -/** - * 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 - */ -public class KeyStore { - - // Used for UID field to indicate the calling UID. - public static final int UID_SELF = -1; -} diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 8c42547caea6..7f936f28ac4f 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -22,7 +22,6 @@ import android.annotation.Nullable; import android.annotation.StringDef; import android.annotation.SystemApi; import android.os.Process; -import android.security.KeyStore; import android.security.keymaster.KeymasterDefs; import libcore.util.EmptyArray; @@ -1008,13 +1007,20 @@ public abstract class KeyProperties { public static final int NAMESPACE_LOCKSETTINGS = 103; /** + * The legacy UID that corresponds to {@link #NAMESPACE_APPLICATION}. + * In new code, prefer to work with Keystore namespaces directly. + * @hide + */ + public static final int UID_SELF = -1; + + /** * For legacy support, translate namespaces into known UIDs. * @hide */ public static int namespaceToLegacyUid(@Namespace int namespace) { switch (namespace) { case NAMESPACE_APPLICATION: - return KeyStore.UID_SELF; + return UID_SELF; case NAMESPACE_WIFI: return Process.WIFI_UID; default: @@ -1029,7 +1035,7 @@ public abstract class KeyProperties { */ public static @Namespace int legacyUidToNamespace(int uid) { switch (uid) { - case KeyStore.UID_SELF: + case UID_SELF: return NAMESPACE_APPLICATION; case Process.WIFI_UID: return NAMESPACE_WIFI; 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..29936cc2cac3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -33,8 +33,11 @@ import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSI import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_RIGHT; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_TOP; -import android.annotation.DimenRes; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.Nullable; +import android.app.Activity; import android.app.ActivityThread; import android.content.Context; import android.content.pm.ActivityInfo; @@ -42,6 +45,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; @@ -51,9 +55,11 @@ import android.view.Gravity; import android.view.MotionEvent; import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; +import android.view.VelocityTracker; import android.view.View; import android.view.WindowManager; import android.view.WindowlessWindowManager; +import android.view.animation.PathInterpolator; import android.widget.FrameLayout; import android.widget.ImageButton; import android.window.InputTransferToken; @@ -95,6 +101,16 @@ class DividerPresenter implements View.OnTouchListener { @VisibleForTesting static final int DEFAULT_DIVIDER_WIDTH_DP = 24; + @VisibleForTesting + static final PathInterpolator FLING_ANIMATION_INTERPOLATOR = + new PathInterpolator(0.4f, 0f, 0.2f, 1f); + @VisibleForTesting + static final int FLING_ANIMATION_DURATION = 250; + @VisibleForTesting + static final int MIN_DISMISS_VELOCITY_DP_PER_SECOND = 600; + @VisibleForTesting + static final int MIN_FLING_VELOCITY_DP_PER_SECOND = 400; + private final int mTaskId; @NonNull @@ -107,6 +123,14 @@ class DividerPresenter implements View.OnTouchListener { private final Executor mCallbackExecutor; /** + * The VelocityTracker of the divider, used to track the dragging velocity. This field is + * {@code null} until dragging starts. + */ + @GuardedBy("mLock") + @Nullable + VelocityTracker mVelocityTracker; + + /** * The {@link Properties} of the divider. This field is {@code null} when no divider should be * drawn, e.g. when the split doesn't have {@link DividerAttributes} or when the decor surface * is not available. @@ -213,7 +237,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 +270,29 @@ 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) { + // This can happen when the activities in the container are from a different process. + // TODO(b/340984203) Report whether the top activity is in the same process. Use default + // color if not. + 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. * @@ -341,13 +392,11 @@ class DividerPresenter implements View.OnTouchListener { applicationContext.getResources().getDisplayMetrics()); } - private static int getDimensionDp(@DimenRes int resId) { - final Context context = ActivityThread.currentActivityThread().getApplication(); - final int px = context.getResources().getDimensionPixelSize(resId); - return (int) TypedValue.convertPixelsToDimension( - COMPLEX_UNIT_DIP, - px, - context.getResources().getDisplayMetrics()); + private static float getDisplayDensity() { + // TODO(b/329193115) support divider on secondary display + final Context applicationContext = + ActivityThread.currentActivityThread().getApplication(); + return applicationContext.getResources().getDisplayMetrics().density; } /** @@ -439,10 +488,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; @@ -462,24 +507,27 @@ class DividerPresenter implements View.OnTouchListener { @Override public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) { synchronized (mLock) { - final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); - mDividerPosition = calculateDividerPosition( - event, taskBounds, mRenderer.mDividerWidthPx, mProperties.mDividerAttributes, - mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition()); - mRenderer.setDividerPosition(mDividerPosition); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - onStartDragging(); - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - onFinishDragging(); - break; - case MotionEvent.ACTION_MOVE: - onDrag(); - break; - default: - break; + if (mProperties != null && mRenderer != null) { + final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); + mDividerPosition = calculateDividerPosition( + event, taskBounds, mRenderer.mDividerWidthPx, + mProperties.mDividerAttributes, mProperties.mIsVerticalSplit, + calculateMinPosition(), calculateMaxPosition()); + mRenderer.setDividerPosition(mDividerPosition); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + onStartDragging(event); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + onFinishDragging(event); + break; + case MotionEvent.ACTION_MOVE: + onDrag(event); + break; + default: + break; + } } } @@ -489,11 +537,17 @@ class DividerPresenter implements View.OnTouchListener { } @GuardedBy("mLock") - private void onStartDragging() { + private void onStartDragging(@NonNull MotionEvent event) { + mVelocityTracker = VelocityTracker.obtain(); + mVelocityTracker.addMovement(event); + mRenderer.mIsDragging = true; mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging); + mRenderer.updateSurface(); + + // Veil visibility change should be applied together with the surface boost transaction in + // the wct. final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mRenderer.updateSurface(t); mRenderer.showVeils(t); // Callbacks must be executed on the executor to release mLock and prevent deadlocks. @@ -508,19 +562,84 @@ class DividerPresenter implements View.OnTouchListener { } @GuardedBy("mLock") - private void onDrag() { - final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mRenderer.updateSurface(t); - t.apply(); + private void onDrag(@NonNull MotionEvent event) { + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(event); + } + mRenderer.updateSurface(); } @GuardedBy("mLock") - private void onFinishDragging() { - mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition); - mRenderer.setDividerPosition(mDividerPosition); + private void onFinishDragging(@NonNull MotionEvent event) { + float velocity = 0.0f; + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(event); + mVelocityTracker.computeCurrentVelocity(1000 /* units */); + velocity = mProperties.mIsVerticalSplit + ? mVelocityTracker.getXVelocity() + : mVelocityTracker.getYVelocity(); + mVelocityTracker.recycle(); + } + + final int prevDividerPosition = mDividerPosition; + mDividerPosition = dividerPositionForSnapPoints(mDividerPosition, velocity); + if (mDividerPosition != prevDividerPosition) { + ValueAnimator animator = getFlingAnimator(prevDividerPosition, mDividerPosition); + animator.start(); + } else { + onDraggingEnd(); + } + } + + @GuardedBy("mLock") + @NonNull + @VisibleForTesting + ValueAnimator getFlingAnimator(int prevDividerPosition, int snappedDividerPosition) { + final ValueAnimator animator = + getValueAnimator(prevDividerPosition, snappedDividerPosition); + animator.addUpdateListener(animation -> { + synchronized (mLock) { + updateDividerPosition((int) animation.getAnimatedValue()); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + synchronized (mLock) { + onDraggingEnd(); + } + } + + @Override + public void onAnimationCancel(Animator animation) { + synchronized (mLock) { + onDraggingEnd(); + } + } + }); + return animator; + } + @VisibleForTesting + static ValueAnimator getValueAnimator(int prevDividerPosition, int snappedDividerPosition) { + ValueAnimator animator = ValueAnimator + .ofInt(prevDividerPosition, snappedDividerPosition) + .setDuration(FLING_ANIMATION_DURATION); + animator.setInterpolator(FLING_ANIMATION_INTERPOLATOR); + return animator; + } + + @GuardedBy("mLock") + private void updateDividerPosition(int position) { + mRenderer.setDividerPosition(position); + mRenderer.updateSurface(); + } + + @GuardedBy("mLock") + private void onDraggingEnd() { + // Veil visibility change should be applied together with the surface boost transaction in + // the wct. final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mRenderer.updateSurface(t); mRenderer.hideVeils(t); // Callbacks must be executed on the executor to release mLock and prevent deadlocks. @@ -542,36 +661,76 @@ class DividerPresenter implements View.OnTouchListener { /** * Returns the divider position adjusted for the min max ratio and fullscreen expansion. - * - * If the dragging position is above the {@link DividerAttributes#getPrimaryMaxRatio()} or below - * {@link DividerAttributes#getPrimaryMinRatio()} and - * {@link DividerAttributes#isDraggingToFullscreenAllowed} is {@code true}, the system will - * choose a snap algorithm to adjust the ending position to either fully expand one container or - * move the divider back to the specified min/max ratio. - * - * TODO(b/327067596) implement snap algorithm - * * The adjusted divider position is in the range of [minPosition, maxPosition] for a split, 0 * for expanded right (bottom) container, or task width (height) minus the divider width for * expanded left (top) container. */ @GuardedBy("mLock") - private int adjustDividerPositionForSnapPoints(int dividerPosition) { + private int dividerPositionForSnapPoints(int dividerPosition, float velocity) { final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); final int minPosition = calculateMinPosition(); final int maxPosition = calculateMaxPosition(); final int fullyExpandedPosition = mProperties.mIsVerticalSplit ? taskBounds.right - mRenderer.mDividerWidthPx : taskBounds.bottom - mRenderer.mDividerWidthPx; + if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) { - if (dividerPosition < minPosition) { - return 0; + final float displayDensity = getDisplayDensity(); + return dividerPositionWithDraggingToFullscreenAllowed( + dividerPosition, + minPosition, + maxPosition, + fullyExpandedPosition, + velocity, + displayDensity); + } + return Math.clamp(dividerPosition, minPosition, maxPosition); + } + + /** + * Returns the divider position given a set of position options. A snap algorithm is used to + * adjust the ending position to either fully expand one container or move the divider back to + * the specified min/max ratio depending on the dragging velocity. + */ + @VisibleForTesting + static int dividerPositionWithDraggingToFullscreenAllowed(int dividerPosition, int minPosition, + int maxPosition, int fullyExpandedPosition, float velocity, float displayDensity) { + final float minDismissVelocityPxPerSecond = + MIN_DISMISS_VELOCITY_DP_PER_SECOND * displayDensity; + final float minFlingVelocityPxPerSecond = + MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity; + if (dividerPosition < minPosition && velocity < -minDismissVelocityPxPerSecond) { + return 0; + } + if (dividerPosition > maxPosition && velocity > minDismissVelocityPxPerSecond) { + return fullyExpandedPosition; + } + if (Math.abs(velocity) < minFlingVelocityPxPerSecond) { + if (dividerPosition >= minPosition && dividerPosition <= maxPosition) { + return dividerPosition; } - if (dividerPosition > maxPosition) { - return fullyExpandedPosition; + int[] possiblePositions = {0, minPosition, maxPosition, fullyExpandedPosition}; + return snap(dividerPosition, possiblePositions); + } + if (velocity < 0) { + return 0; + } else { + return fullyExpandedPosition; + } + } + + /** Calculates the snapped divider position based on the possible positions and distance. */ + private static int snap(int dividerPosition, int[] possiblePositions) { + int snappedPosition = dividerPosition; + float minDistance = Float.MAX_VALUE; + for (int position : possiblePositions) { + float distance = Math.abs(dividerPosition - position); + if (distance < minDistance) { + snappedPosition = position; + minDistance = distance; } } - return Math.clamp(dividerPosition, minPosition, maxPosition); + return snappedPosition; } private static void setDecorSurfaceBoosted( @@ -800,6 +959,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 +971,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 +982,8 @@ class DividerPresenter implements View.OnTouchListener { mIsReversedLayout = isReversedLayout; mDisplayId = displayId; mIsDraggableExpandType = isDraggableExpandType; + mPrimaryVeilColor = primaryVeilColor; + mSecondaryVeilColor = secondaryVeilColor; } /** @@ -840,7 +1005,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 +1044,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 +1076,7 @@ class DividerPresenter implements View.OnTouchListener { context, displayManager.getDisplay(mProperties.mDisplayId), mWindowlessWindowManager, "DividerContainer"); mDividerLayout = new FrameLayout(context); + mDividerLine = new View(context); update(); } @@ -921,6 +1093,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); @@ -947,16 +1130,64 @@ class DividerPresenter implements View.OnTouchListener { * Updates the positions and crops of the divider surface and veil surfaces. This method * should be called when {@link #mProperties} is changed or while dragging to update the * position of the divider surface and the veil surfaces. + * + * This method applies the changes in a stand-alone surface transaction immediately. + */ + private void updateSurface() { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + updateSurface(t); + t.apply(); + } + + /** + * Updates the positions and crops of the divider surface and veil surfaces. This method + * should be called when {@link #mProperties} is changed or while dragging to update the + * position of the divider surface and the veil surfaces. + * + * This method applies the changes in the provided surface transaction and can be synced + * with other changes. */ 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, 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 { - t.setPosition(mDividerSurface, 0.0f, mDividerPosition); - t.setWindowCrop(mDividerSurface, taskBounds.width(), mDividerWidthPx); + // 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 +1202,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 +1226,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 +1265,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 +1325,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/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index a23a47416fb0..f9a6caf42e6e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -21,6 +21,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED; import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; @@ -358,6 +359,13 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { wct.addTaskFragmentOperation(fragmentToken, operation); } + void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct, + @NonNull IBinder fragmentToken, boolean pinned) { + final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_PINNED).setBooleanValue(pinned).build(); + wct.addTaskFragmentOperation(fragmentToken, operation); + } + void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, boolean dimOnTask) { final TaskFragmentOperation operation = new TaskFragmentOperation.Builder( 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 e38038e38c25..b764b6ee065f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -28,6 +28,7 @@ import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO; import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CLOSE; +import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE; import static android.window.TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_OPEN; import static android.window.TaskFragmentTransaction.TYPE_ACTIVITY_REPARENTED_TO_TASK; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_APPEARED; @@ -49,6 +50,7 @@ import static androidx.window.extensions.embedding.SplitPresenter.getActivityInt import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit; +import static androidx.window.extensions.embedding.TaskFragmentContainer.OverlayContainerRestoreParams; import android.annotation.CallbackExecutor; import android.app.Activity; @@ -132,6 +134,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); /** + * Stores the token of the associated Activity that maps to the + * {@link OverlayContainerRestoreParams} of the most recent created overlay container. + */ + @GuardedBy("mLock") + final ArrayMap<IBinder, OverlayContainerRestoreParams> mOverlayRestoreParams = new ArrayMap<>(); + + /** * A developer-defined {@link SplitAttributes} calculator to compute the current * {@link SplitAttributes} with the current device and window states. * It is registered via {@link #setSplitAttributesCalculator(Function)} @@ -349,8 +358,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Resets the isolated navigation and updates the container. final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); final WindowContainerTransaction wct = transactionRecord.getTransaction(); - mPresenter.setTaskFragmentIsolatedNavigation(wct, containerToUnpin, - false /* isolated */); + mPresenter.setTaskFragmentPinned(wct, containerToUnpin, false /* pinned */); updateContainer(wct, containerToUnpin); transactionRecord.apply(false /* shouldApplyIndependently */); updateCallbackIfNecessary(); @@ -686,11 +694,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen exception); break; case TYPE_ACTIVITY_REPARENTED_TO_TASK: + final IBinder candidateAssociatedActToken, lastOverlayToken; + if (Flags.fixPipRestoreToOverlay()) { + candidateAssociatedActToken = change.getOtherActivityToken(); + lastOverlayToken = change.getTaskFragmentToken(); + } else { + candidateAssociatedActToken = lastOverlayToken = null; + } onActivityReparentedToTask( wct, taskId, change.getActivityIntent(), - change.getActivityToken()); + change.getActivityToken(), + candidateAssociatedActToken, + lastOverlayToken); break; default: throw new IllegalArgumentException( @@ -850,6 +867,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen Log.e(TAG, "onTaskFragmentParentInfoChanged on empty Task id=" + taskId); return; } + + 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. + if (taskContainer.isVisible()) { + taskContainer.setInvisible(); + } + return; + } + // Checks if container should be updated before apply new parentInfo. final boolean shouldUpdateContainer = taskContainer.shouldUpdateContainer(parentInfo); taskContainer.updateTaskFragmentParentInfo(parentInfo); @@ -907,11 +934,28 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * different process, the server will generate a temporary token that * the organizer can use to reparent the activity through * {@link WindowContainerTransaction} if needed. + * @param candidateAssociatedActToken The token of the candidate associated-activity. + * @param lastOverlayToken The last parent overlay container token. */ @VisibleForTesting @GuardedBy("mLock") void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, - int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { + int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken, + @Nullable IBinder candidateAssociatedActToken, @Nullable IBinder lastOverlayToken) { + // Reparent the activity to an overlay container if needed. + final OverlayContainerRestoreParams params = getOverlayContainerRestoreParams( + candidateAssociatedActToken, lastOverlayToken); + if (params != null) { + final Activity associatedActivity = getActivity(candidateAssociatedActToken); + final TaskFragmentContainer targetContainer = createOrUpdateOverlayTaskFragmentIfNeeded( + wct, params.mOptions, params.mIntent, associatedActivity); + if (targetContainer != null) { + wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), + activityToken); + return; + } + } + // If the activity belongs to the current app process, we treat it as a new activity // launch. final Activity activity = getActivity(activityToken); @@ -956,6 +1000,43 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** + * Returns the {@link OverlayContainerRestoreParams} that stored last time the {@code + * associatedActivityToken} associated with and only if data matches the {@code overlayToken}. + * Otherwise, return {@code null}. + */ + @VisibleForTesting + @GuardedBy("mLock") + @Nullable + OverlayContainerRestoreParams getOverlayContainerRestoreParams( + @Nullable IBinder associatedActivityToken, @Nullable IBinder overlayToken) { + if (!Flags.fixPipRestoreToOverlay()) { + return null; + } + + if (associatedActivityToken == null || overlayToken == null) { + return null; + } + + final TaskFragmentContainer.OverlayContainerRestoreParams params = + mOverlayRestoreParams.get(associatedActivityToken); + if (params == null) { + return null; + } + + if (params.mOverlayToken != overlayToken) { + // Not the same overlay container, no need to restore. + return null; + } + + final Activity associatedActivity = getActivity(associatedActivityToken); + if (associatedActivity == null || associatedActivity.isFinishing()) { + return null; + } + + return params; + } + + /** * Called when the {@link WindowContainerTransaction} created with * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side. * @@ -1067,8 +1148,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } - // Skip resolving if the activity is on an isolated navigated TaskFragmentContainer. - if (container != null && container.isIsolatedNavigationEnabled()) { + if (container != null && container.shouldSkipActivityResolving()) { return true; } @@ -1424,6 +1504,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen for (int i = mTaskContainers.size() - 1; i >= 0; i--) { mTaskContainers.valueAt(i).onFinishingActivityPaused(wct, activityToken); } + + mOverlayRestoreParams.remove(activity.getActivityToken()); updateCallbackIfNecessary(); } @@ -1441,6 +1523,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen for (int i = mTaskContainers.size() - 1; i >= 0; i--) { mTaskContainers.valueAt(i).onActivityDestroyed(wct, activityToken); } + + mOverlayRestoreParams.remove(activity.getActivityToken()); // We didn't trigger the callback if there were any pending appeared activities, so check // again after the pending is removed. updateCallbackIfNecessary(); @@ -1524,8 +1608,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity( launchingActivity); if (taskFragmentContainer != null - && taskFragmentContainer.isIsolatedNavigationEnabled()) { - // Skip resolving if started from an isolated navigated TaskFragmentContainer. + && taskFragmentContainer.shouldSkipActivityResolving()) { return null; } if (isAssociatedWithOverlay(launchingActivity)) { @@ -3137,11 +3220,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen private static EmbeddedActivityWindowInfo translateActivityWindowInfo( @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) { final boolean isEmbedded = activityWindowInfo.isEmbedded(); - final Rect activityBounds = new Rect(activity.getResources().getConfiguration() - .windowConfiguration.getBounds()); final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds()); final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds()); - return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds, + return new EmbeddedActivityWindowInfo(activity, isEmbedded, taskBounds, activityStackBounds); } @@ -3221,10 +3302,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 @@ -3245,6 +3324,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen synchronized (mLock) { final TransactionRecord transactionRecord = mTransactionManager.startNewTransaction(); + transactionRecord.setOriginType(TASK_FRAGMENT_TRANSIT_DRAG_RESIZE); final WindowContainerTransaction wct = transactionRecord.getTransaction(); final TaskContainer taskContainer = mTaskContainers.get(taskId); if (taskContainer != null) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 0e4fb3075c65..27048136afd8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -401,18 +401,26 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { return; } - setTaskFragmentIsolatedNavigation(wct, secondaryContainer, !isStacked /* isolatedNav */); + setTaskFragmentPinned(wct, secondaryContainer, !isStacked /* pinned */); if (isStacked && !splitPinRule.isSticky()) { secondaryContainer.getTaskContainer().removeSplitPinContainer(); } } /** - * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer} + * Sets whether to enable isolated navigation for this {@link TaskFragmentContainer}. + * <p> + * If a container enables isolated navigation, activities can't be launched to this container + * unless explicitly requested to be launched to. + * + * @see TaskFragmentContainer#isOverlayWithActivityAssociation() */ void setTaskFragmentIsolatedNavigation(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, boolean isolatedNavigationEnabled) { + if (!Flags.activityEmbeddingOverlayPresentationFlag() && container.isOverlay()) { + return; + } if (container.isIsolatedNavigationEnabled() == isolatedNavigationEnabled) { return; } @@ -422,6 +430,28 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } /** + * Sets whether to pin this {@link TaskFragmentContainer}. + * <p> + * If a container is pinned, it won't be chosen as the launch target unless it's the launching + * container. + * + * @see TaskFragmentContainer#isAlwaysOnTopOverlay() + * @see TaskContainer#getSplitPinContainer() + */ + void setTaskFragmentPinned(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container, + boolean pinned) { + if (!Flags.activityEmbeddingOverlayPresentationFlag() && container.isOverlay()) { + return; + } + if (container.isPinned() == pinned) { + return; + } + container.setPinned(pinned); + setTaskFragmentPinned(wct, container.getTaskFragmentToken(), pinned); + } + + /** * Resizes the task fragment if it was already registered. Skips the operation if the container * creation has not been reported from the server yet. */ @@ -586,6 +616,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { super.setCompanionTaskFragment(wct, primary, secondary); } + /** + * Applies the {@code attributes} to a standalone {@code container}. + * + * @param minDimensions the minimum dimension of the container. + */ void applyActivityStackAttributes( @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container, @@ -594,16 +629,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final Rect relativeBounds = sanitizeBounds(attributes.getRelativeBounds(), minDimensions, container); final boolean isFillParent = relativeBounds.isEmpty(); - // Note that we only set isolated navigation for overlay container without activity - // association. Activity will be launched to an expanded container on top of the overlay - // if the overlay is associated with an activity. Thus, an overlay with activity association - // will never be isolated navigated. - final boolean isIsolatedNavigated = container.isAlwaysOnTopOverlay() && !isFillParent; final boolean dimOnTask = !isFillParent - && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK - && Flags.fullscreenDimFlag(); + && Flags.fullscreenDimFlag() + && attributes.getWindowAttributes().getDimAreaBehavior() == DIM_AREA_ON_TASK; final IBinder fragmentToken = container.getTaskFragmentToken(); + if (container.isAlwaysOnTopOverlay()) { + setTaskFragmentPinned(wct, container, !isFillParent); + } else if (container.isOverlayWithActivityAssociation()) { + setTaskFragmentIsolatedNavigation(wct, container, !isFillParent); + } + // TODO(b/243518738): Update to resizeTaskFragment after we migrate WCT#setRelativeBounds // and WCT#setWindowingMode to take fragmentToken. resizeTaskFragmentIfRegistered(wct, container, relativeBounds); @@ -612,7 +648,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { updateTaskFragmentWindowingModeIfRegistered(wct, container, windowingMode); // Always use default animation for standalone ActivityStack. updateAnimationParams(wct, fragmentToken, TaskFragmentAnimationParams.DEFAULT); - setTaskFragmentIsolatedNavigation(wct, container, isIsolatedNavigated); setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask); } 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 67d34c71b4d6..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,39 +137,39 @@ class TaskContainer { } int getDisplayId() { - return mDisplayId; + return mInfo.getDisplayId(); } boolean isVisible() { - return mIsVisible; + return mInfo.isVisible(); + } + + 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; } /** @@ -185,13 +178,15 @@ class TaskContainer { boolean shouldUpdateContainer(@NonNull TaskFragmentParentInfo info) { final Configuration configuration = info.getConfiguration(); - return info.isVisible() - // No need to update presentation in PIP until the Task exit PIP. - && !isInPictureInPicture(configuration) - // If the task properties equals regardless of starting position, don't need to - // update the container. - && (mConfiguration.diffPublicOnly(configuration) != 0 - || mDisplayId != info.getDisplayId()); + if (isInPictureInPicture(configuration)) { + // No need to update presentation in PIP until the Task exit PIP. + return false; + } + + // If the task properties equals regardless of starting position, don't + // need to update the container. + return mInfo.getConfiguration().diffPublicOnly(configuration) != 0 + || mInfo.getDisplayId() != info.getDisplayId(); } /** @@ -218,7 +213,7 @@ class TaskContainer { } boolean isInPictureInPicture() { - return isInPictureInPicture(mConfiguration); + return isInPictureInPicture(mInfo.getConfiguration()); } private static boolean isInPictureInPicture(@NonNull Configuration configuration) { @@ -231,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/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index c952dfea4de5..d0b6a01bb51e 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -36,6 +36,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.window.flags.Flags; import java.util.ArrayList; import java.util.Collections; @@ -184,6 +185,11 @@ class TaskFragmentContainer { private boolean mIsIsolatedNavigationEnabled; /** + * Whether this TaskFragment is pinned. + */ + private boolean mIsPinned; + + /** * Whether to apply dimming on the parent Task that was requested last. */ private boolean mLastDimOnTask; @@ -269,6 +275,15 @@ class TaskFragmentContainer { addPendingAppearedActivity(pendingAppearedActivity); } mPendingAppearedIntent = pendingAppearedIntent; + + // Save the information necessary for restoring the overlay when needed. + if (Flags.fixPipRestoreToOverlay() && overlayTag != null && pendingAppearedIntent != null + && associatedActivity != null && !associatedActivity.isFinishing()) { + final IBinder associatedActivityToken = associatedActivity.getActivityToken(); + final OverlayContainerRestoreParams params = new OverlayContainerRestoreParams(mToken, + launchOptions, pendingAppearedIntent); + mController.mOverlayRestoreParams.put(associatedActivityToken, params); + } } /** @@ -893,6 +908,34 @@ class TaskFragmentContainer { mIsIsolatedNavigationEnabled = isolatedNavigationEnabled; } + /** + * Returns whether this container is pinned. + * + * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED + */ + boolean isPinned() { + return mIsPinned; + } + + /** + * Sets whether to pin this container or not. + * + * @see #isPinned() + */ + void setPinned(boolean pinned) { + mIsPinned = pinned; + } + + /** + * Indicates to skip activity resolving if the activity is from this container. + * + * @see #isIsolatedNavigationEnabled() + * @see #isPinned() + */ + boolean shouldSkipActivityResolving() { + return isIsolatedNavigationEnabled() || isPinned(); + } + /** Sets whether to apply dim on the parent Task. */ void setLastDimOnTask(boolean lastDimOnTask) { mLastDimOnTask = lastDimOnTask; @@ -1072,4 +1115,25 @@ class TaskFragmentContainer { } return sb.append("]").toString(); } + + static class OverlayContainerRestoreParams { + /** The token of the overlay container */ + @NonNull + final IBinder mOverlayToken; + + /** The launch options to create this container. */ + @NonNull + final Bundle mOptions; + + /** The Intent that used to be started in the overlay container. */ + @NonNull + final Intent mIntent; + + OverlayContainerRestoreParams(@NonNull IBinder overlayToken, @NonNull Bundle options, + @NonNull Intent intent) { + mOverlayToken = overlayToken; + mOptions = options; + mIntent = intent; + } + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index 4fd11c495529..070fa5bcfae4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -45,6 +45,7 @@ import androidx.window.common.CommonFoldingFeature; import androidx.window.common.DeviceStateManagerFoldingFeatureProducer; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import androidx.window.extensions.core.util.function.Consumer; +import androidx.window.extensions.util.DeduplicateConsumer; import java.util.ArrayList; import java.util.Collections; @@ -62,7 +63,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { private final Object mLock = new Object(); @GuardedBy("mLock") - private final Map<Context, Consumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = + private final Map<Context, DeduplicateConsumer<WindowLayoutInfo>> mWindowLayoutChangeListeners = new ArrayMap<>(); @GuardedBy("mLock") @@ -130,7 +131,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { if (mWindowLayoutChangeListeners.containsKey(context) // In theory this method can be called on the same consumer with different // context. - || mWindowLayoutChangeListeners.containsValue(consumer)) { + || containsConsumer(consumer)) { return; } if (!context.isUiContext()) { @@ -141,7 +142,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(context, features); consumer.accept(newWindowLayout); }); - mWindowLayoutChangeListeners.put(context, consumer); + mWindowLayoutChangeListeners.put(context, new DeduplicateConsumer<>(consumer)); final IBinder windowContextToken = context.getWindowContextToken(); if (windowContextToken != null) { @@ -176,19 +177,35 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { @Override public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) { synchronized (mLock) { + DeduplicateConsumer<WindowLayoutInfo> consumerToRemove = null; for (Context context : mWindowLayoutChangeListeners.keySet()) { - if (!mWindowLayoutChangeListeners.get(context).equals(consumer)) { + final DeduplicateConsumer<WindowLayoutInfo> deduplicateConsumer = + mWindowLayoutChangeListeners.get(context); + if (!deduplicateConsumer.matchesConsumer(consumer)) { continue; } final IBinder token = context.getWindowContextToken(); + consumerToRemove = deduplicateConsumer; if (token != null) { context.unregisterComponentCallbacks(mConfigurationChangeListeners.get(token)); mConfigurationChangeListeners.remove(token); } break; } - mWindowLayoutChangeListeners.values().remove(consumer); + if (consumerToRemove != null) { + mWindowLayoutChangeListeners.values().remove(consumerToRemove); + } + } + } + + @GuardedBy("mLock") + private boolean containsConsumer(@NonNull Consumer<WindowLayoutInfo> consumer) { + for (DeduplicateConsumer<WindowLayoutInfo> c : mWindowLayoutChangeListeners.values()) { + if (c.matchesConsumer(consumer)) { + return true; + } } + return false; } @GuardedBy("mLock") diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/util/DeduplicateConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/util/DeduplicateConsumer.java new file mode 100644 index 000000000000..ee271aa57003 --- /dev/null +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/util/DeduplicateConsumer.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.extensions.util; + +import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.window.extensions.core.util.function.Consumer; + +/** + * A utility class that will not report a value if it is the same as the last reported value. + * @param <T> generic values to be reported. + */ +public class DeduplicateConsumer<T> implements Consumer<T> { + + private final Object mLock = new Object(); + @GuardedBy("mLock") + @Nullable + private T mLastReportedValue = null; + @NonNull + private final Consumer<T> mConsumer; + + public DeduplicateConsumer(@NonNull Consumer<T> consumer) { + mConsumer = consumer; + } + + /** + * Returns {@code true} if the given consumer matches this object or the wrapped + * {@link Consumer}, {@code false} otherwise + */ + public boolean matchesConsumer(@NonNull Consumer<T> consumer) { + return consumer == this || mConsumer.equals(consumer); + } + + /** + * Accepts a new value and relays it if it is different from + * the last reported value. + * @param value to report if different. + */ + @Override + public void accept(@NonNull T value) { + synchronized (mLock) { + if (mLastReportedValue != null && mLastReportedValue.equals(value)) { + return; + } + mLastReportedValue = value; + } + mConsumer.accept(value); + } +} 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/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp index 4ddbd13978d5..61ea51a35f58 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp +++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp @@ -23,6 +23,7 @@ package { android_test { name: "WMJetpackUnitTests", + team: "trendy_team_windowing_sdk", // To make the test run via TEST_MAPPING test_suites: ["device-tests"], @@ -32,6 +33,7 @@ android_test { static_libs: [ "androidx.window.extensions", + "androidx.window.extensions.core_core", "junit", "androidx.test.runner", "androidx.test.rules", 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..746607c8094c 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 @@ -19,6 +19,10 @@ package androidx.window.extensions.embedding; import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE; import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE; +import static androidx.window.extensions.embedding.DividerPresenter.FLING_ANIMATION_DURATION; +import static androidx.window.extensions.embedding.DividerPresenter.FLING_ANIMATION_INTERPOLATOR; +import static androidx.window.extensions.embedding.DividerPresenter.MIN_DISMISS_VELOCITY_DP_PER_SECOND; +import static androidx.window.extensions.embedding.DividerPresenter.MIN_FLING_VELOCITY_DP_PER_SECOND; import static androidx.window.extensions.embedding.DividerPresenter.getBoundsOffsetForDivider; import static androidx.window.extensions.embedding.DividerPresenter.getInitialDividerPosition; import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM; @@ -35,8 +39,12 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.ValueAnimator; +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 +52,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 +162,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 +617,130 @@ 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 non-null, its background color should be returned. + when(container.getTopNonFinishingActivity()).thenReturn(activity); + assertEquals(activityBackgroundColor, + DividerPresenter.getContainerBackgroundColor(container, defaultColor)); + } + + @Test + public void testGetValueAnimator() { + ValueAnimator animator = + DividerPresenter.getValueAnimator( + 375 /* prevDividerPosition */, + 500 /* snappedDividerPosition */); + + assertEquals(animator.getDuration(), FLING_ANIMATION_DURATION); + assertEquals(animator.getInterpolator(), FLING_ANIMATION_INTERPOLATOR); + } + + @Test + public void testDividerPositionWithDraggingToFullscreenAllowed() { + final float displayDensity = 600F; + final float dismissVelocity = MIN_DISMISS_VELOCITY_DP_PER_SECOND * displayDensity + 10f; + final float nonFlingVelocity = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity - 10f; + final float flingVelocity = MIN_FLING_VELOCITY_DP_PER_SECOND * displayDensity + 10f; + + // Divider position is less than minPosition and the velocity is enough to be dismissed + assertEquals( + 0, // Closed position + DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + 10 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + -dismissVelocity, + displayDensity)); + + // Divider position is greater than maxPosition and the velocity is enough to be dismissed + assertEquals( + 1200, // Fully expanded position + DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + 1000 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + dismissVelocity, + displayDensity)); + + // Divider position is returned when the velocity is not fast enough for fling and is in + // between minPosition and maxPosition + assertEquals( + 500, // dividerPosition is not snapped + DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + 500 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + nonFlingVelocity, + displayDensity)); + + // Divider position is snapped when the velocity is not fast enough for fling and larger + // than maxPosition + assertEquals( + 900, // Closest position is maxPosition + DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + 950 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + nonFlingVelocity, + displayDensity)); + + // Divider position is snapped when the velocity is not fast enough for fling and smaller + // than minPosition + assertEquals( + 30, // Closest position is minPosition + DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + 20 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + nonFlingVelocity, + displayDensity)); + + // Divider position is greater than minPosition and the velocity is enough for fling + assertEquals( + 0, // Closed position + DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + 50 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + -flingVelocity, + displayDensity)); + + // Divider position is less than maxPosition and the velocity is enough for fling + assertEquals( + 1200, // Fully expanded position + DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( + 800 /* dividerPosition */, + 30 /* minPosition */, + 900 /* maxPosition */, + 1200 /* fullyExpandedPosition */, + flingVelocity, + displayDensity)); + } + private TaskFragmentContainer createMockTaskFragmentContainer( @NonNull IBinder token, @NonNull Rect bounds) { final TaskFragmentContainer container = mock(TaskFragmentContainer.class); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index fab298d6d58b..f3222572a3e2 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -188,6 +188,32 @@ public class OverlayPresentationTest { } @Test + public void testSetIsolatedNavigation_overlayFeatureDisabled_earlyReturn() { + mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG); + + final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test"); + + mSplitPresenter.setTaskFragmentIsolatedNavigation(mTransaction, container, + !container.isIsolatedNavigationEnabled()); + + verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(), + any(IBinder.class), anyBoolean()); + } + + @Test + public void testSetPinned_overlayFeatureDisabled_earlyReturn() { + mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG); + + final TaskFragmentContainer container = createTestOverlayContainer(TASK_ID, "test"); + + mSplitPresenter.setTaskFragmentPinned(mTransaction, container, + !container.isPinned()); + + verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), any(IBinder.class), + anyBoolean()); + } + + @Test public void testGetAllNonFinishingOverlayContainers() { assertThat(mSplitController.getAllNonFinishingOverlayContainers()).isEmpty(); @@ -580,7 +606,7 @@ public class OverlayPresentationTest { final TaskContainer.TaskProperties taskProperties = taskContainer.getTaskProperties(); final TaskFragmentParentInfo parentInfo = new TaskFragmentParentInfo( new Configuration(taskProperties.getConfiguration()), taskProperties.getDisplayId(), - false /* visible */, false /* hasDirectActivity */, null /* decorSurface */); + true /* visible */, false /* hasDirectActivity */, null /* decorSurface */); mSplitController.onTaskFragmentParentInfoChanged(mTransaction, TASK_ID, parentInfo); @@ -608,8 +634,11 @@ public class OverlayPresentationTest { WINDOWING_MODE_UNDEFINED); verify(mSplitPresenter).updateAnimationParams(mTransaction, token, TaskFragmentAnimationParams.DEFAULT); - verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false); verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false); + verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), + any(TaskFragmentContainer.class), anyBoolean()); + verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(), + any(TaskFragmentContainer.class), anyBoolean()); } @Test @@ -630,9 +659,9 @@ public class OverlayPresentationTest { WINDOWING_MODE_MULTI_WINDOW); verify(mSplitPresenter).updateAnimationParams(mTransaction, token, TaskFragmentAnimationParams.DEFAULT); - // Set isolated navigation to false if the overlay container is associated with - // the launching activity. - verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false); + verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, true); + verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), + any(TaskFragmentContainer.class), anyBoolean()); verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true); } @@ -655,10 +684,9 @@ public class OverlayPresentationTest { container, WINDOWING_MODE_MULTI_WINDOW); verify(mSplitPresenter).updateAnimationParams(mTransaction, token, TaskFragmentAnimationParams.DEFAULT); - // Set isolated navigation to false if the overlay container is associated with - // the launching activity. - verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, - container, true); + verify(mSplitPresenter, never()).setTaskFragmentIsolatedNavigation(any(), + any(TaskFragmentContainer.class), anyBoolean()); + verify(mSplitPresenter).setTaskFragmentPinned(mTransaction, container, true); verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, true); } @@ -678,6 +706,8 @@ public class OverlayPresentationTest { verify(mSplitPresenter).updateAnimationParams(mTransaction, token, TaskFragmentAnimationParams.DEFAULT); verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, container, false); + verify(mSplitPresenter, never()).setTaskFragmentPinned(any(), + any(TaskFragmentContainer.class), anyBoolean()); verify(mSplitPresenter).setTaskFragmentDimOnTask(mTransaction, token, false); } @@ -806,6 +836,30 @@ public class OverlayPresentationTest { any()); } + @Test + public void testOnActivityReparentedToTask_overlayRestoration() { + mSetFlagRule.enableFlags(Flags.FLAG_FIX_PIP_RESTORE_TO_OVERLAY); + + // Prepares and mock the data necessary for the test. + final IBinder activityToken = mActivity.getActivityToken(); + final Intent intent = new Intent(); + final IBinder fillTaskActivityToken = new Binder(); + final IBinder lastOverlayToken = new Binder(); + final TaskFragmentContainer overlayContainer = mSplitController.newContainer(intent, + mActivity, TASK_ID); + final TaskFragmentContainer.OverlayContainerRestoreParams params = mock( + TaskFragmentContainer.OverlayContainerRestoreParams.class); + doReturn(params).when(mSplitController).getOverlayContainerRestoreParams(any(), any()); + doReturn(overlayContainer).when(mSplitController).createOrUpdateOverlayTaskFragmentIfNeeded( + any(), any(), any(), any()); + + // Verify the activity should be reparented to the overlay container. + mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken, + fillTaskActivityToken, lastOverlayToken); + verify(mTransaction).reparentActivityToTaskFragment( + eq(overlayContainer.getTaskFragmentToken()), eq(activityToken)); + } + /** * A simplified version of {@link SplitController#createOrUpdateOverlayTaskFragmentIfNeeded} */ diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 8bc3a300136a..35353dbe36be 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -397,7 +397,8 @@ public class SplitControllerTest { @Test public void testOnActivityReparentedToTask_sameProcess() { mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(), - mActivity.getActivityToken()); + mActivity.getActivityToken(), null /* fillTaskActivityToken */, + null /* lastOverlayToken */); // Treated as on activity created, but allow to split as primary. verify(mSplitController).resolveActivityToContainer(mTransaction, @@ -413,7 +414,8 @@ public class SplitControllerTest { final IBinder activityToken = new Binder(); final Intent intent = new Intent(); - mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken); + mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken, + null /* fillTaskActivityToken */, null /* lastOverlayToken */); // Treated as starting new intent verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean()); @@ -1210,7 +1212,7 @@ public class SplitControllerTest { mSplitController.onTransactionReady(transaction); verify(mSplitController).onActivityReparentedToTask(any(), eq(TASK_ID), eq(intent), - eq(activityToken)); + eq(activityToken), any(), any()); verify(mSplitPresenter).onTransactionHandled(eq(transaction.getTransactionToken()), any(), anyInt(), anyBoolean()); } @@ -1570,8 +1572,6 @@ public class SplitControllerTest { mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG); final boolean isEmbedded = true; - final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration - .getBounds(); final Rect taskBounds = new Rect(0, 0, 1000, 2000); final Rect activityStackBounds = new Rect(0, 0, 500, 2000); doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded(); @@ -1579,7 +1579,7 @@ public class SplitControllerTest { doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds(); final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity, - isEmbedded, activityBounds, taskBounds, activityStackBounds); + isEmbedded, taskBounds, activityStackBounds); assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity)); } @@ -1621,6 +1621,48 @@ public class SplitControllerTest { verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any()); } + @Test + public void testTaskFragmentParentInfoChanged() { + // Making a split + final Activity secondaryActivity = createMockActivity(); + addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */); + + // Updates the parent info. + final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); + final Configuration configuration = new Configuration(); + final TaskFragmentParentInfo originalInfo = new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */); + mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class), + TASK_ID, originalInfo); + assertTrue(taskContainer.isVisible()); + + // Making a public configuration change while the Task is invisible. + configuration.densityDpi += 100; + final TaskFragmentParentInfo invisibleInfo = new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, false /* visible */, false /* hasDirectActivity */, + null /* decorSurface */); + mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class), + TASK_ID, invisibleInfo); + + // Ensure the TaskContainer is inivisible, but the configuration is not updated. + assertFalse(taskContainer.isVisible()); + assertTrue(taskContainer.getTaskFragmentParentInfo().getConfiguration().diffPublicOnly( + configuration) > 0); + + // Updates when Task to become visible + final TaskFragmentParentInfo visibleInfo = new TaskFragmentParentInfo(configuration, + DEFAULT_DISPLAY, true /* visible */, false /* hasDirectActivity */, + null /* decorSurface */); + mSplitController.onTaskFragmentParentInfoChanged(mock(WindowContainerTransaction.class), + TASK_ID, visibleInfo); + + // Ensure the Task is visible and configuration is updated. + assertTrue(taskContainer.isVisible()); + assertFalse(taskContainer.getTaskFragmentParentInfo().getConfiguration().diffPublicOnly( + configuration) > 0); + } + /** Creates a mock activity in the organizer process. */ private Activity createMockActivity() { return createMockActivity(TASK_ID); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index c677484f64f1..3fbce9ec31a5 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED; import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY; import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY; @@ -285,6 +286,28 @@ public class SplitPresenterTest { } @Test + public void testSetTaskFragmentPinned() { + final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + + // Verify the default. + assertFalse(container.isPinned()); + + mPresenter.setTaskFragmentPinned(mTransaction, container, true); + + final TaskFragmentOperation expectedOperation = new TaskFragmentOperation.Builder( + OP_TYPE_SET_PINNED).setBooleanValue(true).build(); + verify(mTransaction).addTaskFragmentOperation(container.getTaskFragmentToken(), + expectedOperation); + assertTrue(container.isPinned()); + + // No request to set the same animation params. + clearInvocations(mTransaction); + mPresenter.setTaskFragmentPinned(mTransaction, container, true); + + verify(mTransaction, never()).addTaskFragmentOperation(any(), any()); + } + + @Test public void testGetMinDimensionsForIntent() { final Intent intent = new Intent(ApplicationProvider.getApplicationContext(), MinimumDimensionActivity.class); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/util/DeduplicateConsumerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/util/DeduplicateConsumerTest.java new file mode 100644 index 000000000000..4e9b4a02e1f8 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/util/DeduplicateConsumerTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.window.extensions.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import androidx.window.extensions.core.util.function.Consumer; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * A class to validate {@link DeduplicateConsumer}. + */ +public class DeduplicateConsumerTest { + + @Test + public void test_duplicate_value_is_filtered() { + String value = "test_value"; + List<String> expected = new ArrayList<>(); + expected.add(value); + RecordingConsumer recordingConsumer = new RecordingConsumer(); + DeduplicateConsumer<String> deduplicateConsumer = + new DeduplicateConsumer<>(recordingConsumer); + + deduplicateConsumer.accept(value); + deduplicateConsumer.accept(value); + + assertEquals(expected, recordingConsumer.getValues()); + } + + @Test + public void test_different_value_is_filtered() { + String value = "test_value"; + String newValue = "test_value_new"; + List<String> expected = new ArrayList<>(); + expected.add(value); + expected.add(newValue); + RecordingConsumer recordingConsumer = new RecordingConsumer(); + DeduplicateConsumer<String> deduplicateConsumer = + new DeduplicateConsumer<>(recordingConsumer); + + deduplicateConsumer.accept(value); + deduplicateConsumer.accept(value); + deduplicateConsumer.accept(newValue); + + assertEquals(expected, recordingConsumer.getValues()); + } + + @Test + public void test_match_against_consumer_property_returns_true() { + RecordingConsumer recordingConsumer = new RecordingConsumer(); + DeduplicateConsumer<String> deduplicateConsumer = + new DeduplicateConsumer<>(recordingConsumer); + + assertTrue(deduplicateConsumer.matchesConsumer(recordingConsumer)); + } + + @Test + public void test_match_against_self_returns_true() { + RecordingConsumer recordingConsumer = new RecordingConsumer(); + DeduplicateConsumer<String> deduplicateConsumer = + new DeduplicateConsumer<>(recordingConsumer); + + assertTrue(deduplicateConsumer.matchesConsumer(deduplicateConsumer)); + } + + private static final class RecordingConsumer implements Consumer<String> { + + private final List<String> mValues = new ArrayList<>(); + + @Override + public void accept(String s) { + mValues.add(s); + } + + public List<String> getValues() { + return mValues; + } + } +} diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 7ff204c695f8..66d48799204c 100644 --- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig +++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig @@ -64,3 +64,24 @@ flag { description: "Enables long-press action for nav handle when a bubble is expanded" bug: "324910035" } + +flag { + name: "enable_optional_bubble_overflow" + namespace: "multitasking" + description: "Hides the bubble overflow if there aren't any overflowed bubbles" + bug: "334175587" +} + +flag { + name: "enable_retrievable_bubbles" + namespace: "multitasking" + description: "Allow opening bubbles overflow UI without bubbles being visible" + bug: "340337839" +} + +flag { + name: "enable_bubble_stashing" + namespace: "multitasking" + description: "Allow the floating bubble stack to stash on the edge of the screen" + bug: "341361249" +} diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt index 8487e3792993..9e1440d5716b 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt @@ -218,11 +218,10 @@ class BubblePositionerTest { insets = Insets.of(10, 20, 5, 15), windowBounds = Rect(0, 0, 1800, 2600) ) - val bubbleBarBounds = Rect(1700, 2500, 1780, 2600) positioner.setShowingInBubbleBar(true) positioner.update(deviceConfig) - positioner.bubbleBarBounds = bubbleBarBounds + positioner.bubbleBarTopOnScreen = 2500 val spaceBetweenTopInsetAndBubbleBarInLandscape = 1680 val expandedViewVerticalSpacing = @@ -246,10 +245,9 @@ class BubblePositionerTest { insets = Insets.of(10, 20, 5, 15), windowBounds = Rect(0, 0, screenWidth, 2600) ) - val bubbleBarBounds = Rect(100, 2500, 280, 2550) positioner.setShowingInBubbleBar(true) positioner.update(deviceConfig) - positioner.bubbleBarBounds = bubbleBarBounds + positioner.bubbleBarTopOnScreen = 2500 val spaceBetweenTopInsetAndBubbleBarInLandscape = 180 val expandedViewSpacing = @@ -597,16 +595,19 @@ class BubblePositionerTest { private fun testGetBubbleBarExpandedViewBounds(onLeft: Boolean, isOverflow: Boolean) { positioner.setShowingInBubbleBar(true) + val windowBounds = Rect(0, 0, 2000, 2600) + val insets = Insets.of(10, 20, 5, 15) val deviceConfig = defaultDeviceConfig.copy( isLargeScreen = true, isLandscape = true, - insets = Insets.of(10, 20, 5, 15), - windowBounds = Rect(0, 0, 2000, 2600) + insets = insets, + windowBounds = windowBounds ) positioner.update(deviceConfig) - positioner.bubbleBarBounds = getBubbleBarBounds(onLeft, deviceConfig) + val bubbleBarHeight = 100 + positioner.bubbleBarTopOnScreen = windowBounds.bottom - insets.bottom - bubbleBarHeight val expandedViewPadding = context.resources.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding) @@ -624,7 +625,7 @@ class BubblePositionerTest { left = right - positioner.getExpandedViewWidthForBubbleBar(isOverflow) } // Above the bubble bar - val bottom = positioner.bubbleBarBounds.top - expandedViewPadding + val bottom = positioner.bubbleBarTopOnScreen - expandedViewPadding // Calculate right and top based on size val top = bottom - positioner.getExpandedViewHeightForBubbleBar(isOverflow) val expectedBounds = Rect(left, top, right, bottom) @@ -666,21 +667,4 @@ class BubblePositionerTest { positioner.getAllowableStackPositionRegion(1 /* bubbleCount */) return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent } - - private fun getBubbleBarBounds(onLeft: Boolean, deviceConfig: DeviceConfig): Rect { - val width = 200 - val height = 100 - val bottom = deviceConfig.windowBounds.bottom - deviceConfig.insets.bottom - val top = bottom - height - val left: Int - val right: Int - if (onLeft) { - left = deviceConfig.insets.left - right = left + width - } else { - right = deviceConfig.windowBounds.right - deviceConfig.insets.right - left = right - width - } - return Rect(left, top, right, bottom) - } } 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..12d19279111a 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 @@ -54,7 +53,6 @@ class BubbleExpandedViewPinControllerTest { const val SCREEN_WIDTH = 2000 const val SCREEN_HEIGHT = 1000 - const val BUBBLE_BAR_WIDTH = 100 const val BUBBLE_BAR_HEIGHT = 50 } @@ -85,14 +83,8 @@ class BubbleExpandedViewPinControllerTest { insets = Insets.of(10, 20, 30, 40) ) positioner.update(deviceConfig) - positioner.bubbleBarBounds = - Rect( - SCREEN_WIDTH - deviceConfig.insets.right - BUBBLE_BAR_WIDTH, - SCREEN_HEIGHT - deviceConfig.insets.bottom - BUBBLE_BAR_HEIGHT, - SCREEN_WIDTH - deviceConfig.insets.right, - SCREEN_HEIGHT - deviceConfig.insets.bottom - ) - + positioner.bubbleBarTopOnScreen = + SCREEN_HEIGHT - deviceConfig.insets.bottom - BUBBLE_BAR_HEIGHT controller = BubbleExpandedViewPinController(context, container, positioner) testListener = TestLocationChangeListener() controller.setListener(testListener) @@ -248,16 +240,10 @@ 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) { InstrumentationRegistry.getInstrumentation().runOnMainSync(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/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml index 9f0a425a82f8..9599658384f0 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml @@ -23,7 +23,8 @@ android:orientation="horizontal" android:gravity="center" android:padding="16dp" - android:background="@drawable/desktop_mode_maximize_menu_background"> + android:background="@drawable/desktop_mode_maximize_menu_background" + android:elevation="1dp"> <LinearLayout android:layout_width="wrap_content" @@ -37,7 +38,8 @@ android:background="@drawable/desktop_mode_maximize_menu_layout_background" android:padding="4dp" android:layout_marginRight="8dp" - android:layout_marginBottom="4dp"> + android:layout_marginBottom="4dp" + android:alpha="0"> <Button android:id="@+id/maximize_menu_maximize_button" style="?android:attr/buttonBarButtonStyle" @@ -48,6 +50,7 @@ </FrameLayout> <TextView + android:id="@+id/maximize_menu_maximize_window_text" android:layout_width="94dp" android:layout_height="18dp" android:textSize="11sp" @@ -55,7 +58,8 @@ android:gravity="center" android:fontFamily="google-sans-text" android:text="@string/desktop_mode_maximize_menu_maximize_text" - android:textColor="?androidprv:attr/materialColorOnSurface"/> + android:textColor="?androidprv:attr/materialColorOnSurface" + android:alpha="0"/> </LinearLayout> <LinearLayout @@ -69,7 +73,8 @@ android:orientation="horizontal" android:padding="4dp" android:background="@drawable/desktop_mode_maximize_menu_layout_background" - android:layout_marginBottom="4dp"> + android:layout_marginBottom="4dp" + android:alpha="0"> <Button android:id="@+id/maximize_menu_snap_left_button" style="?android:attr/buttonBarButtonStyle" @@ -88,6 +93,7 @@ android:stateListAnimator="@null"/> </LinearLayout> <TextView + android:id="@+id/maximize_menu_snap_window_text" android:layout_width="94dp" android:layout_height="18dp" android:textSize="11sp" @@ -96,6 +102,8 @@ android:gravity="center" android:fontFamily="google-sans-text" android:text="@string/desktop_mode_maximize_menu_snap_text" - android:textColor="?androidprv:attr/materialColorOnSurface"/> + android:textColor="?androidprv:attr/materialColorOnSurface" + android:alpha="0"/> </LinearLayout> -</LinearLayout>
\ No newline at end of file +</LinearLayout> + diff --git a/libs/WindowManager/Shell/res/values-af/strings.xml b/libs/WindowManager/Shell/res/values-af/strings.xml index b9ff5c682b42..1c8f5e60c5c9 100644 --- a/libs/WindowManager/Shell/res/values-af/strings.xml +++ b/libs/WindowManager/Shell/res/values-af/strings.xml @@ -53,7 +53,7 @@ <string name="accessibility_split_top" msgid="2789329702027147146">"Verdeel bo"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"Verdeel onder"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Gebruik eenhandmodus"</string> - <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Swiep van die onderkant van die skerm af op of tik enige plek bo die program om uit te gaan"</string> + <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Swiep van die onderkant van die skerm af op of tik enige plek bo die app om uit te gaan"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Begin eenhandmodus"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Verlaat eenhandmodus"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Instellings vir <xliff:g id="APP_NAME">%1$s</xliff:g>-borrels"</string> diff --git a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml index 1fde4cfb76f8..3492f136c4f9 100644 --- a/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr-rCA/strings.xml @@ -53,7 +53,7 @@ <string name="accessibility_split_top" msgid="2789329702027147146">"Diviser dans la partie supérieure"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"Diviser dans la partie inférieure"</string> <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode Une main"</string> - <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'application"</string> + <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran du bas vers le haut, ou touchez n\'importe où sur l\'écran en haut de l\'appli"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode Une main"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Quitter le mode Une main"</string> <string name="bubbles_settings_button_description" msgid="1301286017420516912">"Paramètres pour les bulles de l\'application <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> @@ -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-fr/strings.xml b/libs/WindowManager/Shell/res/values-fr/strings.xml index e7233ae029b5..4002e4d04d51 100644 --- a/libs/WindowManager/Shell/res/values-fr/strings.xml +++ b/libs/WindowManager/Shell/res/values-fr/strings.xml @@ -52,7 +52,7 @@ <string name="accessibility_split_right" msgid="8441001008181296837">"Affichée à droite"</string> <string name="accessibility_split_top" msgid="2789329702027147146">"Affichée en haut"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"Affichée en haut"</string> - <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utiliser le mode une main"</string> + <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Utilisation du mode une main"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Pour quitter, balayez l\'écran de bas en haut ou appuyez n\'importe où au-dessus de l\'application"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Démarrer le mode une main"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Quitter le mode une main"</string> diff --git a/libs/WindowManager/Shell/res/values-hr/strings.xml b/libs/WindowManager/Shell/res/values-hr/strings.xml index fc3942095fdf..27d4cfcf22d5 100644 --- a/libs/WindowManager/Shell/res/values-hr/strings.xml +++ b/libs/WindowManager/Shell/res/values-hr/strings.xml @@ -52,7 +52,7 @@ <string name="accessibility_split_right" msgid="8441001008181296837">"Podijeli desno"</string> <string name="accessibility_split_top" msgid="2789329702027147146">"Podijeli gore"</string> <string name="accessibility_split_bottom" msgid="8694551025220868191">"Podijeli dolje"</string> - <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Korištenje načina rada jednom rukom"</string> + <string name="one_handed_tutorial_title" msgid="4583241688067426350">"Upotreba načina rada jednom rukom"</string> <string name="one_handed_tutorial_description" msgid="3486582858591353067">"Za izlaz prijeđite prstom od dna zaslona prema gore ili dodirnite bio gdje iznad aplikacije"</string> <string name="accessibility_action_start_one_handed" msgid="5070337354072861426">"Pokretanje načina rada jednom rukom"</string> <string name="accessibility_action_stop_one_handed" msgid="1369940261782179442">"Izlaz iz načina rada jednom rukom"</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 c8bfe7a4ef80..aa4fb4448ac7 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -247,13 +247,11 @@ <!-- Padding for the bubble popup view contents. --> <dimen name="bubble_popup_padding">24dp</dimen> <!-- The size of the caption bar inset at the top of bubble bar expanded view. --> - <dimen name="bubble_bar_expanded_view_caption_height">32dp</dimen> + <dimen name="bubble_bar_expanded_view_caption_height">36dp</dimen> <!-- The width of the caption bar at the top of bubble bar expanded view. --> - <dimen name="bubble_bar_expanded_view_caption_width">128dp</dimen> - <!-- The height of the dots shown for the caption menu in the bubble bar expanded view.. --> - <dimen name="bubble_bar_expanded_view_caption_dot_size">4dp</dimen> - <!-- The spacing between the dots for the caption menu in the bubble bar expanded view.. --> - <dimen name="bubble_bar_expanded_view_caption_dot_spacing">4dp</dimen> + <dimen name="bubble_bar_expanded_view_caption_width">80dp</dimen> + <!-- The height of the handle shown for the caption menu in the bubble bar expanded view. --> + <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen> <!-- Width of the expanded bubble bar view shown when the bubble is expanded. --> <dimen name="bubble_bar_expanded_view_width">412dp</dimen> <!-- Minimum width of the bubble bar manage menu. --> @@ -274,6 +272,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 --> @@ -464,6 +465,9 @@ <!-- The height of the maximize menu in desktop mode. --> <dimen name="desktop_mode_maximize_menu_height">114dp</dimen> + <!-- The padding of the maximize menu in desktop mode. --> + <dimen name="desktop_mode_menu_padding">16dp</dimen> + <!-- The height of the buttons in the maximize menu. --> <dimen name="desktop_mode_maximize_menu_button_height">52dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java index fcddcad3a949..8d8655addc65 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/DesktopModeStatus.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.desktopmode; +package com.android.wm.shell.shared; import android.annotation.NonNull; import android.content.Context; @@ -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); @@ -76,6 +67,16 @@ public class DesktopModeStatus { private static final boolean ENFORCE_DEVICE_RESTRICTIONS = SystemProperties.getBoolean( "persist.wm.debug.desktop_mode_enforce_device_restrictions", true); + /** Override density for tasks when they're inside the desktop. */ + public static final int DESKTOP_DENSITY_OVERRIDE = + SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284); + + /** The minimum override density allowed for tasks inside the desktop. */ + private static final int DESKTOP_DENSITY_MIN = 100; + + /** The maximum override density allowed for tasks inside the desktop. */ + private static final int DESKTOP_DENSITY_MAX = 1000; + /** * Default value for {@code MAX_TASK_LIMIT}. */ @@ -93,8 +94,10 @@ public class DesktopModeStatus { "persist.wm.debug.desktop_max_task_limit", DEFAULT_MAX_TASK_LIMIT); /** - * Return {@code true} if desktop windowing is enabled + * Return {@code true} if desktop windowing is enabled. Only to be used for testing. Callers + * should use {@link #canEnterDesktopMode(Context)} to query the state of desktop windowing. */ + @VisibleForTesting public static boolean isEnabled() { return Flags.enableDesktopWindowingMode(); } @@ -107,14 +110,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 @@ -142,7 +137,7 @@ public class DesktopModeStatus { /** * Return the maximum limit on the number of Tasks to show in Desktop Mode at any one time. */ - static int getMaxTaskLimit() { + public static int getMaxTaskLimit() { return MAX_TASK_LIMIT; } @@ -155,9 +150,17 @@ public class DesktopModeStatus { } /** - * Return {@code true} if desktop mode can be entered on the current device. + * Return {@code true} if desktop mode is enabled and can be entered on the current device. */ public static boolean canEnterDesktopMode(@NonNull Context context) { - return !enforceDeviceRestrictions() || isDesktopModeSupported(context); + return (!enforceDeviceRestrictions() || isDesktopModeSupported(context)) && isEnabled(); + } + + /** + * Return {@code true} if the override desktop density is set. + */ + public static boolean isDesktopDensityOverrideSet() { + return DESKTOP_DENSITY_OVERRIDE >= DESKTOP_DENSITY_MIN + && DESKTOP_DENSITY_OVERRIDE <= DESKTOP_DENSITY_MAX; } } diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java index dcd4062cb819..6ca6517abbb0 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java @@ -69,8 +69,12 @@ public class TransitionUtil { /** Returns {@code true} if the transition is opening or closing mode. */ public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) { - return mode == TRANSIT_OPEN || mode == TRANSIT_CLOSE - || mode == TRANSIT_TO_FRONT || mode == TRANSIT_TO_BACK; + return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK; + } + + /** Returns {@code true} if the transition is opening mode. */ + public static boolean isOpeningMode(@TransitionInfo.TransitionMode int mode) { + return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT; } /** Returns {@code true} if the transition has a display change. */ @@ -318,7 +322,7 @@ public class TransitionUtil { null, new Rect(change.getStartAbsBounds()), taskInfo, - change.getAllowEnterPip(), + change.isAllowEnterPip(), INVALID_WINDOW_TYPE ); target.setWillShowImeOnTarget( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java index d44033c72302..a426b206b0cd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java @@ -26,6 +26,7 @@ import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationS import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition; import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow; import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet; +import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE; import android.animation.Animator; import android.animation.ValueAnimator; @@ -190,6 +191,10 @@ class ActivityEmbeddingAnimationRunner { @NonNull private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters( @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) { + if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) { + // Jump cut for AE drag resizing because the content is veiled. + return new ArrayList<>(); + } boolean isChangeTransition = false; for (TransitionInfo.Change change : info.getChanges()) { if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 1f9358e2aa91..d6b9d34c5ab3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -22,6 +22,7 @@ import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static com.android.wm.shell.transition.DefaultTransitionHandler.isSupportedOverrideAnimation; +import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE; import static java.util.Objects.requireNonNull; @@ -90,6 +91,12 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle /** Whether ActivityEmbeddingController should animate this transition. */ public boolean shouldAnimate(@NonNull TransitionInfo info) { + if (info.getType() == TRANSIT_TASK_FRAGMENT_DRAG_RESIZE) { + // The TRANSIT_TASK_FRAGMENT_DRAG_RESIZE type happens when the user drags the + // interactive divider to resize the split containers. The content is veiled, so we will + // handle the transition with a jump cut. + return true; + } boolean containsEmbeddingChange = false; for (TransitionInfo.Change change : info.getChanges()) { if (!change.hasFlags(FLAG_FILLS_TASK) && change.hasFlags( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 163a896e2659..5600664a8f47 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -646,7 +646,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private void tryDispatchOnBackCancelled(IOnBackInvokedCallback callback) { if (!mOnBackStartDispatched) { - Log.e(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched."); + Log.d(TAG, "Skipping dispatching onBackCancelled. Start was never dispatched."); return; } if (callback == null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index 037b1ec9247c..c988c2fb5103 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.wm.shell.back import android.animation.Animator @@ -34,6 +35,7 @@ import android.view.RemoteAnimationTarget import android.view.SurfaceControl import android.view.animation.DecelerateInterpolator import android.view.animation.Interpolator +import android.view.animation.Transformation import android.window.BackEvent import android.window.BackMotionEvent import android.window.BackNavigationInfo @@ -46,52 +48,45 @@ import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.animation.Interpolators import com.android.wm.shell.protolog.ShellProtoLogGroup -import com.android.wm.shell.shared.annotations.ShellMainThread -import javax.inject.Inject import kotlin.math.abs import kotlin.math.max import kotlin.math.min -/** Class that defines cross-activity animation. */ -@ShellMainThread -class CrossActivityBackAnimation @Inject constructor( +abstract class CrossActivityBackAnimation( private val context: Context, private val background: BackAnimationBackground, - private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + protected val transaction: SurfaceControl.Transaction, + private val choreographer: Choreographer ) : ShellBackAnimation() { - private val startClosingRect = RectF() - private val targetClosingRect = RectF() - private val currentClosingRect = RectF() + protected val startClosingRect = RectF() + protected val targetClosingRect = RectF() + protected val currentClosingRect = RectF() - private val startEnteringRect = RectF() - private val targetEnteringRect = RectF() - private val currentEnteringRect = RectF() + protected val startEnteringRect = RectF() + protected val targetEnteringRect = RectF() + protected val currentEnteringRect = RectF() - private val backAnimRect = Rect() + protected val backAnimRect = Rect() private val cropRect = Rect() private var cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) - private val backAnimationRunner = BackAnimationRunner( - Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY - ) + private val backAnimationRunner = + BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY) private val initialTouchPos = PointF() private val transformMatrix = Matrix() private val tmpFloat9 = FloatArray(9) - private var enteringTarget: RemoteAnimationTarget? = null - private var closingTarget: RemoteAnimationTarget? = null - private val transaction = SurfaceControl.Transaction() + protected var enteringTarget: RemoteAnimationTarget? = null + protected var closingTarget: RemoteAnimationTarget? = null private var triggerBack = false private var finishCallback: IRemoteAnimationFinishedCallback? = null private val progressAnimator = BackProgressAnimator() private val displayBoundsMargin = context.resources.getDimension(R.dimen.cross_task_back_vertical_margin) - private val enteringStartOffset = - context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset) private val gestureInterpolator = Interpolators.BACK_GESTURE - private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN private val verticalMoveInterpolator: Interpolator = DecelerateInterpolator() private var scrimLayer: SurfaceControl? = null @@ -103,13 +98,42 @@ class CrossActivityBackAnimation @Inject constructor( private var rightLetterboxLayer: SurfaceControl? = null private var letterboxColor: Int = 0 + /** Background color to be used during the animation, also see [getBackgroundColor] */ + protected var customizedBackgroundColor = 0 + + /** + * Whether the entering target should be shifted vertically with the user gesture in pre-commit + */ + abstract val allowEnteringYShift: Boolean + + /** + * Subclasses must set the [startEnteringRect] and [targetEnteringRect] to define the movement + * of the enteringTarget during pre-commit phase. + */ + abstract fun preparePreCommitEnteringRectMovement() + + /** + * Returns a base transformation to apply to the entering target during pre-commit. The system + * will apply the default animation on top of it. + */ + protected open fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation? = + null + override fun onConfigurationChanged(newConfiguration: Configuration) { cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context) } override fun getRunner() = backAnimationRunner - private fun startBackAnimation(backMotionEvent: BackMotionEvent) { + private fun getBackgroundColor(): Int = + when { + customizedBackgroundColor != 0 -> customizedBackgroundColor + isLetterboxed -> letterboxColor + enteringTarget != null -> enteringTarget!!.taskInfo.taskDescription!!.backgroundColor + else -> 0 + } + + protected open fun startBackAnimation(backMotionEvent: BackMotionEvent) { if (enteringTarget == null || closingTarget == null) { ProtoLog.d( ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, @@ -122,8 +146,8 @@ class CrossActivityBackAnimation @Inject constructor( transaction.setAnimationTransaction() isLetterboxed = closingTarget!!.taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed - enteringHasSameLetterbox = isLetterboxed && - closingTarget!!.localBounds.equals(enteringTarget!!.localBounds) + enteringHasSameLetterbox = + isLetterboxed && closingTarget!!.localBounds.equals(enteringTarget!!.localBounds) if (isLetterboxed && !enteringHasSameLetterbox) { // Play animation with letterboxes, if closing and entering target have mismatching @@ -143,32 +167,27 @@ class CrossActivityBackAnimation @Inject constructor( targetClosingRect.scaleCentered(MAX_SCALE) if (backMotionEvent.swipeEdge != BackEvent.EDGE_RIGHT) { targetClosingRect.offset( - startClosingRect.right - targetClosingRect.right - displayBoundsMargin, 0f + startClosingRect.right - targetClosingRect.right - displayBoundsMargin, + 0f ) } - // the entering target starts 96dp to the left of the screen edge... - startEnteringRect.set(startClosingRect) - startEnteringRect.offset(-enteringStartOffset, 0f) - - // ...and gets scaled in sync with the closing target - targetEnteringRect.set(startEnteringRect) - targetEnteringRect.scaleCentered(MAX_SCALE) + preparePreCommitEnteringRectMovement() - // Draw background with task background color (or letterbox color). - val backgroundColor = if (isLetterboxed) { - letterboxColor - } else { - enteringTarget!!.taskInfo.taskDescription!!.backgroundColor - } background.ensureBackground( - closingTarget!!.windowConfiguration.bounds, backgroundColor, transaction + closingTarget!!.windowConfiguration.bounds, + getBackgroundColor(), + transaction ) ensureScrimLayer() if (isLetterboxed && enteringHasSameLetterbox) { // crop left and right letterboxes - cropRect.set(closingTarget!!.localBounds.left, 0, closingTarget!!.localBounds.right, - closingTarget!!.windowConfiguration.bounds.height()) + cropRect.set( + closingTarget!!.localBounds.left, + 0, + closingTarget!!.localBounds.right, + closingTarget!!.windowConfiguration.bounds.height() + ) // and add fake letterbox square surfaces instead ensureLetterboxes() } else { @@ -185,8 +204,14 @@ class CrossActivityBackAnimation @Inject constructor( currentClosingRect.offset(0f, yOffset) applyTransform(closingTarget?.leash, currentClosingRect, 1f) currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress) - currentEnteringRect.offset(0f, yOffset) - applyTransform(enteringTarget?.leash, currentEnteringRect, 1f) + if (allowEnteringYShift) currentEnteringRect.offset(0f, yOffset) + val enteringTransformation = getPreCommitEnteringBaseTransformation(progress) + applyTransform( + enteringTarget?.leash, + currentEnteringRect, + enteringTransformation?.alpha ?: 1f, + enteringTransformation + ) applyTransaction() } @@ -199,30 +224,25 @@ class CrossActivityBackAnimation @Inject constructor( val deltaYRatio = min(screenHeight / 2f, abs(rawYDelta)) / (screenHeight / 2f) val interpolatedYRatio: Float = verticalMoveInterpolator.getInterpolation(deltaYRatio) // limit y-shift so surface never passes 8dp screen margin - val deltaY = yDirection * interpolatedYRatio * max( - 0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin - ) + val deltaY = + max(0f, (screenHeight - centeredRect.height()) / 2f - displayBoundsMargin) * + interpolatedYRatio * + yDirection return deltaY } - private fun onGestureCommitted() { - if (closingTarget?.leash == null || enteringTarget?.leash == null || - !enteringTarget!!.leash.isValid || !closingTarget!!.leash.isValid + protected open fun onGestureCommitted() { + if ( + closingTarget?.leash == null || + enteringTarget?.leash == null || + !enteringTarget!!.leash.isValid || + !closingTarget!!.leash.isValid ) { finishAnimation() return } - // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current - // coordinate of the gesture driven phase. Let's update the start and target rects and kick - // off the animator - startClosingRect.set(currentClosingRect) - startEnteringRect.set(currentEnteringRect) - targetEnteringRect.set(backAnimRect) - targetClosingRect.set(backAnimRect) - targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f) - - val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_ANIMATION_DURATION) + val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(POST_COMMIT_DURATION) valueAnimator.addUpdateListener { animation: ValueAnimator -> val progress = animation.animatedFraction onPostCommitProgress(progress) @@ -230,27 +250,22 @@ class CrossActivityBackAnimation @Inject constructor( background.resetStatusBarCustomization() } } - valueAnimator.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - background.resetStatusBarCustomization() - finishAnimation() + valueAnimator.addListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + background.resetStatusBarCustomization() + finishAnimation() + } } - }) + ) valueAnimator.start() } - private fun onPostCommitProgress(linearProgress: Float) { - val closingAlpha = max(1f - linearProgress * 2, 0f) - val progress = postCommitInterpolator.getInterpolation(linearProgress) + protected open fun onPostCommitProgress(linearProgress: Float) { scrimLayer?.let { transaction.setAlpha(it, maxScrimAlpha * (1f - linearProgress)) } - currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress) - applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha) - currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress) - applyTransform(enteringTarget?.leash, currentEnteringRect, 1f) - applyTransaction() } - private fun finishAnimation() { + protected open fun finishAnimation() { enteringTarget?.let { if (it.leash != null && it.leash.isValid) { transaction.setCornerRadius(it.leash, 0f) @@ -278,47 +293,56 @@ class CrossActivityBackAnimation @Inject constructor( enteringHasSameLetterbox = false } - private fun applyTransform(leash: SurfaceControl?, rect: RectF, alpha: Float) { + protected fun applyTransform( + leash: SurfaceControl?, + rect: RectF, + alpha: Float, + baseTransformation: Transformation? = null + ) { if (leash == null || !leash.isValid) return val scale = rect.width() / backAnimRect.width() - transformMatrix.reset() - val scalePivotX = if (isLetterboxed && enteringHasSameLetterbox) { - closingTarget!!.localBounds.left.toFloat() - } else { - 0f - } - transformMatrix.setScale(scale, scale, scalePivotX, 0f) - transformMatrix.postTranslate(rect.left, rect.top) - transaction.setAlpha(leash, alpha) - .setMatrix(leash, transformMatrix, tmpFloat9) + val matrix = baseTransformation?.matrix ?: transformMatrix.apply { reset() } + val scalePivotX = + if (isLetterboxed && enteringHasSameLetterbox) { + closingTarget!!.localBounds.left.toFloat() + } else { + 0f + } + matrix.postScale(scale, scale, scalePivotX, 0f) + matrix.postTranslate(rect.left, rect.top) + transaction + .setAlpha(leash, keepMinimumAlpha(alpha)) + .setMatrix(leash, matrix, tmpFloat9) .setCrop(leash, cropRect) .setCornerRadius(leash, cornerRadius) } - private fun applyTransaction() { - transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId) + protected fun applyTransaction() { + transaction.setFrameTimelineVsync(choreographer.vsyncId) transaction.apply() } private fun ensureScrimLayer() { if (scrimLayer != null) return val isDarkTheme: Boolean = isDarkMode(context) - val scrimBuilder = SurfaceControl.Builder() - .setName("Cross-Activity back animation scrim") - .setCallsite("CrossActivityBackAnimation") - .setColorLayer() - .setOpaque(false) - .setHidden(false) + val scrimBuilder = + SurfaceControl.Builder() + .setName("Cross-Activity back animation scrim") + .setCallsite("CrossActivityBackAnimation") + .setColorLayer() + .setOpaque(false) + .setHidden(false) rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, scrimBuilder) scrimLayer = scrimBuilder.build() val colorComponents = floatArrayOf(0f, 0f, 0f) maxScrimAlpha = if (isDarkTheme) MAX_SCRIM_ALPHA_DARK else MAX_SCRIM_ALPHA_LIGHT - val scrimCrop = if (isLetterboxed) { - closingTarget!!.windowConfiguration.bounds - } else { - closingTarget!!.localBounds - } + val scrimCrop = + if (isLetterboxed) { + closingTarget!!.windowConfiguration.bounds + } else { + closingTarget!!.localBounds + } transaction .setColor(scrimLayer, colorComponents) .setAlpha(scrimLayer!!, maxScrimAlpha) @@ -339,21 +363,34 @@ class CrossActivityBackAnimation @Inject constructor( private fun ensureLetterboxes() { closingTarget?.let { t -> if (t.localBounds.left != 0 && leftLetterboxLayer == null) { - val bounds = Rect(0, t.windowConfiguration.bounds.top, t.localBounds.left, - t.windowConfiguration.bounds.bottom) + val bounds = + Rect( + 0, + t.windowConfiguration.bounds.top, + t.localBounds.left, + t.windowConfiguration.bounds.bottom + ) leftLetterboxLayer = ensureLetterbox(bounds) } - if (t.localBounds.right != t.windowConfiguration.bounds.right && - rightLetterboxLayer == null) { - val bounds = Rect(t.localBounds.right, t.windowConfiguration.bounds.top, - t.windowConfiguration.bounds.right, t.windowConfiguration.bounds.bottom) + if ( + t.localBounds.right != t.windowConfiguration.bounds.right && + rightLetterboxLayer == null + ) { + val bounds = + Rect( + t.localBounds.right, + t.windowConfiguration.bounds.top, + t.windowConfiguration.bounds.right, + t.windowConfiguration.bounds.bottom + ) rightLetterboxLayer = ensureLetterbox(bounds) } } } private fun ensureLetterbox(bounds: Rect): SurfaceControl { - val letterboxBuilder = SurfaceControl.Builder() + val letterboxBuilder = + SurfaceControl.Builder() .setName("Cross-Activity back animation letterbox") .setCallsite("CrossActivityBackAnimation") .setColorLayer() @@ -362,13 +399,17 @@ class CrossActivityBackAnimation @Inject constructor( rootTaskDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, letterboxBuilder) val layer = letterboxBuilder.build() - val colorComponents = floatArrayOf(Color.red(letterboxColor) / 255f, - Color.green(letterboxColor) / 255f, Color.blue(letterboxColor) / 255f) + val colorComponents = + floatArrayOf( + Color.red(letterboxColor) / 255f, + Color.green(letterboxColor) / 255f, + Color.blue(letterboxColor) / 255f + ) transaction - .setColor(layer, colorComponents) - .setCrop(layer, bounds) - .setRelativeLayer(layer, closingTarget!!.leash, 1) - .show(layer) + .setColor(layer, colorComponents) + .setCrop(layer, bounds) + .setRelativeLayer(layer, closingTarget!!.leash, 1) + .show(layer) return layer } @@ -389,8 +430,8 @@ class CrossActivityBackAnimation @Inject constructor( } override fun prepareNextAnimation( - animationInfo: BackNavigationInfo.CustomAnimationInfo?, - letterboxColor: Int + animationInfo: BackNavigationInfo.CustomAnimationInfo?, + letterboxColor: Int ): Boolean { this.letterboxColor = letterboxColor return false @@ -415,9 +456,7 @@ class CrossActivityBackAnimation @Inject constructor( } override fun onBackCancelled() { - progressAnimator.onBackCancelled { - finishAnimation() - } + progressAnimator.onBackCancelled { finishAnimation() } } override fun onBackInvoked() { @@ -435,7 +474,8 @@ class CrossActivityBackAnimation @Inject constructor( finishedCallback: IRemoteAnimationFinishedCallback ) { ProtoLog.d( - ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "Start back to activity animation." + ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, + "Start back to activity animation." ) for (a in apps) { when (a.mode) { @@ -452,23 +492,25 @@ class CrossActivityBackAnimation @Inject constructor( } companion object { - /** Max scale of the entering/closing window.*/ - private const val MAX_SCALE = 0.9f - - /** Duration of post animation after gesture committed. */ - private const val POST_ANIMATION_DURATION = 300L - + /** Max scale of the closing window. */ + internal const val MAX_SCALE = 0.9f private const val MAX_SCRIM_ALPHA_DARK = 0.8f private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f + private const val POST_COMMIT_DURATION = 300L } } +// The target will loose focus when alpha == 0, so keep a minimum value for it. +private fun keepMinimumAlpha(transAlpha: Float): Float { + return max(transAlpha.toDouble(), 0.005).toFloat() +} + private fun isDarkMode(context: Context): Boolean { return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == - Configuration.UI_MODE_NIGHT_YES + Configuration.UI_MODE_NIGHT_YES } -private fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) { +internal fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Float) { require(!(progress < 0 || progress > 1)) { "Progress value must be between 0 and 1" } left = start.left + (target.left - start.left) * progress top = start.top + (target.top - start.top) * progress @@ -476,7 +518,7 @@ private fun RectF.setInterpolatedRectF(start: RectF, target: RectF, progress: Fl bottom = start.bottom + (target.bottom - start.bottom) * progress } -private fun RectF.scaleCentered( +internal fun RectF.scaleCentered( scale: Float, pivotX: Float = left + width() / 2, pivotY: Float = top + height() / 2 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt new file mode 100644 index 000000000000..e6ec2b449616 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.back + +import android.content.Context +import android.graphics.Rect +import android.graphics.RectF +import android.util.MathUtils +import android.view.Choreographer +import android.view.SurfaceControl +import android.view.animation.Animation +import android.view.animation.Transformation +import android.window.BackMotionEvent +import android.window.BackNavigationInfo +import com.android.internal.R +import com.android.internal.policy.TransitionAnimation +import com.android.internal.protolog.common.ProtoLog +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.annotations.ShellMainThread +import javax.inject.Inject + +/** Class that handles customized predictive cross activity back animations. */ +@ShellMainThread +class CustomCrossActivityBackAnimation( + context: Context, + background: BackAnimationBackground, + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + transaction: SurfaceControl.Transaction, + choreographer: Choreographer, + private val customAnimationLoader: CustomAnimationLoader +) : + CrossActivityBackAnimation( + context, + background, + rootTaskDisplayAreaOrganizer, + transaction, + choreographer + ) { + + private var enterAnimation: Animation? = null + private var closeAnimation: Animation? = null + private val transformation = Transformation() + private var gestureProgress = 0f + + override val allowEnteringYShift = false + + @Inject + constructor( + context: Context, + background: BackAnimationBackground, + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + ) : this( + context, + background, + rootTaskDisplayAreaOrganizer, + SurfaceControl.Transaction(), + Choreographer.getInstance(), + CustomAnimationLoader( + TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation") + ) + ) + + override fun preparePreCommitEnteringRectMovement() { + // No movement for the entering rect + startEnteringRect.set(startClosingRect) + targetEnteringRect.set(startClosingRect) + } + + override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation { + gestureProgress = progress + transformation.clear() + enterAnimation!!.getTransformationAt(progress * PRE_COMMIT_MAX_PROGRESS, transformation) + return transformation + } + + override fun startBackAnimation(backMotionEvent: BackMotionEvent) { + super.startBackAnimation(backMotionEvent) + if ( + closeAnimation == null || + enterAnimation == null || + closingTarget == null || + enteringTarget == null + ) { + ProtoLog.d( + ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, + "Enter animation or close animation is null." + ) + return + } + initializeAnimation(closeAnimation!!, closingTarget!!.localBounds) + initializeAnimation(enterAnimation!!, enteringTarget!!.localBounds) + } + + override fun onPostCommitProgress(linearProgress: Float) { + super.onPostCommitProgress(linearProgress) + if (closingTarget == null || enteringTarget == null) return + + // TODO: Should we use the duration from the custom xml spec for the post-commit animation? + applyTransform(closingTarget!!.leash, currentClosingRect, linearProgress, closeAnimation!!) + val enteringProgress = + MathUtils.lerp(gestureProgress * PRE_COMMIT_MAX_PROGRESS, 1f, linearProgress) + applyTransform( + enteringTarget!!.leash, + currentEnteringRect, + enteringProgress, + enterAnimation!! + ) + applyTransaction() + } + + private fun applyTransform( + leash: SurfaceControl, + rect: RectF, + progress: Float, + animation: Animation + ) { + transformation.clear() + animation.getTransformationAt(progress, transformation) + applyTransform(leash, rect, transformation.alpha, transformation) + } + + override fun finishAnimation() { + closeAnimation?.reset() + closeAnimation = null + enterAnimation?.reset() + enterAnimation = null + transformation.clear() + gestureProgress = 0f + super.finishAnimation() + } + + /** Load customize animation before animation start. */ + override fun prepareNextAnimation( + animationInfo: BackNavigationInfo.CustomAnimationInfo?, + letterboxColor: Int + ): Boolean { + super.prepareNextAnimation(animationInfo, letterboxColor) + if (animationInfo == null) return false + customAnimationLoader.loadAll(animationInfo)?.let { result -> + closeAnimation = result.closeAnimation + enterAnimation = result.enterAnimation + customizedBackgroundColor = result.backgroundColor + return true + } + return false + } + + class AnimationLoadResult { + var closeAnimation: Animation? = null + var enterAnimation: Animation? = null + var backgroundColor = 0 + } + + companion object { + private const val PRE_COMMIT_MAX_PROGRESS = 0.2f + } +} + +/** Helper class to load custom animation. */ +class CustomAnimationLoader(private val transitionAnimation: TransitionAnimation) { + + /** + * Load both enter and exit animation for the close activity transition. Note that the result is + * only valid if the exit animation has set and loaded success. If the entering animation has + * not set(i.e. 0), here will load the default entering animation for it. + * + * @param animationInfo The information of customize animation, which can be set from + * [Activity.overrideActivityTransition] and/or [LayoutParams.windowAnimations] + */ + fun loadAll( + animationInfo: BackNavigationInfo.CustomAnimationInfo + ): CustomCrossActivityBackAnimation.AnimationLoadResult? { + if (animationInfo.packageName.isEmpty()) return null + val close = loadAnimation(animationInfo, false) ?: return null + val open = loadAnimation(animationInfo, true) + val result = CustomCrossActivityBackAnimation.AnimationLoadResult() + result.closeAnimation = close + result.enterAnimation = open + result.backgroundColor = animationInfo.customBackground + return result + } + + /** + * Load enter or exit animation from CustomAnimationInfo + * + * @param animationInfo The information for customize animation. + * @param enterAnimation true when load for enter animation, false for exit animation. + * @return Loaded animation. + */ + fun loadAnimation( + animationInfo: BackNavigationInfo.CustomAnimationInfo, + enterAnimation: Boolean + ): Animation? { + var a: Animation? = null + // Activity#overrideActivityTransition has higher priority than windowAnimations + // Try to get animation from Activity#overrideActivityTransition + if ( + enterAnimation && animationInfo.customEnterAnim != 0 || + !enterAnimation && animationInfo.customExitAnim != 0 + ) { + a = + transitionAnimation.loadAppTransitionAnimation( + animationInfo.packageName, + if (enterAnimation) animationInfo.customEnterAnim + else animationInfo.customExitAnim + ) + } else if (animationInfo.windowAnimations != 0) { + // try to get animation from LayoutParams#windowAnimations + a = + transitionAnimation.loadAnimationAttr( + animationInfo.packageName, + animationInfo.windowAnimations, + if (enterAnimation) R.styleable.WindowAnimation_activityCloseEnterAnimation + else R.styleable.WindowAnimation_activityCloseExitAnimation, + false /* translucent */ + ) + } + // Only allow to load default animation for opening target. + if (a == null && enterAnimation) { + a = loadDefaultOpenAnimation() + } + if (a != null) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a) + } else { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW, "No custom animation loaded") + } + return a + } + + private fun loadDefaultOpenAnimation(): Animation? { + return transitionAnimation.loadDefaultAnimationAttr( + R.styleable.WindowAnimation_activityCloseEnterAnimation, + false /* translucent */ + ) + } +} + +private fun initializeAnimation(animation: Animation, bounds: Rect) { + val width = bounds.width() + val height = bounds.height() + animation.initialize(width, height, width, height) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java deleted file mode 100644 index e27b40e58591..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.back; - -import static android.view.RemoteAnimationTarget.MODE_CLOSING; -import static android.view.RemoteAnimationTarget.MODE_OPENING; - -import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY; -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.Activity; -import android.content.Context; -import android.content.res.Configuration; -import android.graphics.Color; -import android.graphics.Rect; -import android.os.RemoteException; -import android.util.FloatProperty; -import android.view.Choreographer; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.WindowManager.LayoutParams; -import android.view.animation.Animation; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.Transformation; -import android.window.BackEvent; -import android.window.BackMotionEvent; -import android.window.BackNavigationInfo; -import android.window.BackProgressAnimator; -import android.window.IOnBackInvokedCallback; - -import com.android.internal.R; -import com.android.internal.dynamicanimation.animation.SpringAnimation; -import com.android.internal.dynamicanimation.animation.SpringForce; -import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.policy.TransitionAnimation; -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.shared.annotations.ShellMainThread; - -import javax.inject.Inject; - -/** Class that handle customized close activity transition animation. */ -@ShellMainThread -public class CustomizeActivityAnimation extends ShellBackAnimation { - private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator(); - private final BackAnimationRunner mBackAnimationRunner; - private float mCornerRadius; - private final SurfaceControl.Transaction mTransaction; - private final BackAnimationBackground mBackground; - private RemoteAnimationTarget mEnteringTarget; - private RemoteAnimationTarget mClosingTarget; - private IRemoteAnimationFinishedCallback mFinishCallback; - /** Duration of post animation after gesture committed. */ - private static final int POST_ANIMATION_DURATION = 250; - - private static final int SCALE_FACTOR = 1000; - private final SpringAnimation mProgressSpring; - private float mLatestProgress = 0.0f; - - private static final float TARGET_COMMIT_PROGRESS = 0.5f; - - private final float[] mTmpFloat9 = new float[9]; - private final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator(); - - final CustomAnimationLoader mCustomAnimationLoader; - private Animation mEnterAnimation; - private Animation mCloseAnimation; - private int mNextBackgroundColor; - final Transformation mTransformation = new Transformation(); - - private final Choreographer mChoreographer; - private final Context mContext; - - @Inject - public CustomizeActivityAnimation(Context context, BackAnimationBackground background) { - this(context, background, new SurfaceControl.Transaction(), null); - } - - CustomizeActivityAnimation(Context context, BackAnimationBackground background, - SurfaceControl.Transaction transaction, Choreographer choreographer) { - mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context); - mBackground = background; - mBackAnimationRunner = new BackAnimationRunner( - new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY); - mCustomAnimationLoader = new CustomAnimationLoader(context); - - mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP); - mProgressSpring.setSpring(new SpringForce() - .setStiffness(SpringForce.STIFFNESS_MEDIUM) - .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); - mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction; - mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance(); - mContext = context; - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(mContext); - } - - private float getLatestProgress() { - return mLatestProgress * SCALE_FACTOR; - } - private void setLatestProgress(float value) { - mLatestProgress = value / SCALE_FACTOR; - applyTransformTransaction(mLatestProgress); - } - - private static final FloatProperty<CustomizeActivityAnimation> ENTER_PROGRESS_PROP = - new FloatProperty<>("enter") { - @Override - public void setValue(CustomizeActivityAnimation anim, float value) { - anim.setLatestProgress(value); - } - - @Override - public Float get(CustomizeActivityAnimation object) { - return object.getLatestProgress(); - } - }; - - // The target will lose focus when alpha == 0, so keep a minimum value for it. - private static float keepMinimumAlpha(float transAlpha) { - return Math.max(transAlpha, 0.005f); - } - - private static void initializeAnimation(Animation animation, Rect bounds) { - final int width = bounds.width(); - final int height = bounds.height(); - animation.initialize(width, height, width, height); - } - - private void startBackAnimation() { - if (mEnteringTarget == null || mClosingTarget == null - || mCloseAnimation == null || mEnterAnimation == null) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null."); - return; - } - initializeAnimation(mCloseAnimation, mClosingTarget.localBounds); - initializeAnimation(mEnterAnimation, mEnteringTarget.localBounds); - - // Draw background with task background color. - if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) { - mBackground.ensureBackground(mClosingTarget.windowConfiguration.getBounds(), - mNextBackgroundColor == Color.TRANSPARENT - ? mEnteringTarget.taskInfo.taskDescription.getBackgroundColor() - : mNextBackgroundColor, - mTransaction); - } - } - - private void applyTransformTransaction(float progress) { - if (mClosingTarget == null || mEnteringTarget == null) { - return; - } - applyTransform(mClosingTarget.leash, progress, mCloseAnimation); - applyTransform(mEnteringTarget.leash, progress, mEnterAnimation); - mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId()); - mTransaction.apply(); - } - - private void applyTransform(SurfaceControl leash, float progress, Animation animation) { - mTransformation.clear(); - animation.getTransformationAt(progress, mTransformation); - mTransaction.setMatrix(leash, mTransformation.getMatrix(), mTmpFloat9); - mTransaction.setAlpha(leash, keepMinimumAlpha(mTransformation.getAlpha())); - mTransaction.setCornerRadius(leash, mCornerRadius); - } - - void finishAnimation() { - if (mCloseAnimation != null) { - mCloseAnimation.reset(); - mCloseAnimation = null; - } - if (mEnterAnimation != null) { - mEnterAnimation.reset(); - mEnterAnimation = null; - } - if (mEnteringTarget != null) { - mEnteringTarget.leash.release(); - mEnteringTarget = null; - } - if (mClosingTarget != null) { - mClosingTarget.leash.release(); - mClosingTarget = null; - } - if (mBackground != null) { - mBackground.removeBackground(mTransaction); - } - mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId()); - mTransaction.apply(); - mTransformation.clear(); - mLatestProgress = 0; - mNextBackgroundColor = Color.TRANSPARENT; - if (mFinishCallback != null) { - try { - mFinishCallback.onAnimationFinished(); - } catch (RemoteException e) { - e.printStackTrace(); - } - mFinishCallback = null; - } - mProgressSpring.animateToFinalPosition(0); - mProgressSpring.skipToEnd(); - } - - void onGestureProgress(@NonNull BackEvent backEvent) { - if (mEnteringTarget == null || mClosingTarget == null - || mCloseAnimation == null || mEnterAnimation == null) { - return; - } - - final float progress = backEvent.getProgress(); - - float springProgress = (progress > 0.1f - ? mapLinear(progress, 0.1f, 1f, TARGET_COMMIT_PROGRESS, 1f) - : mapLinear(progress, 0, 1f, 0f, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR; - - mProgressSpring.animateToFinalPosition(springProgress); - } - - static float mapLinear(float x, float a1, float a2, float b1, float b2) { - return b1 + (x - a1) * (b2 - b1) / (a2 - a1); - } - - void onGestureCommitted() { - if (mEnteringTarget == null || mClosingTarget == null - || mCloseAnimation == null || mEnterAnimation == null) { - finishAnimation(); - return; - } - mProgressSpring.cancel(); - - // Enter phase 2 of the animation - final ValueAnimator valueAnimator = ValueAnimator.ofFloat(mLatestProgress, 1f) - .setDuration(POST_ANIMATION_DURATION); - valueAnimator.setInterpolator(mDecelerateInterpolator); - valueAnimator.addUpdateListener(animation -> { - float progress = (float) animation.getAnimatedValue(); - applyTransformTransaction(progress); - }); - - valueAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - finishAnimation(); - } - }); - valueAnimator.start(); - } - - /** Load customize animation before animation start. */ - @Override - public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo, - int letterboxColor) { - if (animationInfo == null) { - return false; - } - final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo); - if (result != null) { - mCloseAnimation = result.mCloseAnimation; - mEnterAnimation = result.mEnterAnimation; - mNextBackgroundColor = result.mBackgroundColor; - return true; - } - return false; - } - - @Override - public BackAnimationRunner getRunner() { - return mBackAnimationRunner; - } - - private final class Callback extends IOnBackInvokedCallback.Default { - @Override - public void onBackStarted(BackMotionEvent backEvent) { - // in case we're still animating an onBackCancelled event, let's remove the finish- - // callback from the progress animator to prevent calling finishAnimation() before - // restarting a new animation - mProgressAnimator.removeOnBackCancelledFinishCallback(); - - mProgressAnimator.onBackStarted(backEvent, - CustomizeActivityAnimation.this::onGestureProgress); - } - - @Override - public void onBackProgressed(@NonNull BackMotionEvent backEvent) { - mProgressAnimator.onBackProgressed(backEvent); - } - - @Override - public void onBackCancelled() { - mProgressAnimator.onBackCancelled(CustomizeActivityAnimation.this::finishAnimation); - } - - @Override - public void onBackInvoked() { - mProgressAnimator.reset(); - onGestureCommitted(); - } - } - - private final class Runner extends IRemoteAnimationRunner.Default { - @Override - public void onAnimationStart( - int transit, - RemoteAnimationTarget[] apps, - RemoteAnimationTarget[] wallpapers, - RemoteAnimationTarget[] nonApps, - IRemoteAnimationFinishedCallback finishedCallback) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to customize animation."); - for (RemoteAnimationTarget a : apps) { - if (a.mode == MODE_CLOSING) { - mClosingTarget = a; - } - if (a.mode == MODE_OPENING) { - mEnteringTarget = a; - } - } - if (mCloseAnimation == null || mEnterAnimation == null) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, - "No animation loaded, should choose cross-activity animation?"); - } - - startBackAnimation(); - mFinishCallback = finishedCallback; - } - - @Override - public void onAnimationCancelled() { - finishAnimation(); - } - } - - - static final class AnimationLoadResult { - Animation mCloseAnimation; - Animation mEnterAnimation; - int mBackgroundColor; - } - - /** - * Helper class to load custom animation. - */ - static class CustomAnimationLoader { - final TransitionAnimation mTransitionAnimation; - - CustomAnimationLoader(Context context) { - mTransitionAnimation = new TransitionAnimation( - context, false /* debug */, "CustomizeBackAnimation"); - } - - /** - * Load both enter and exit animation for the close activity transition. - * Note that the result is only valid if the exit animation has set and loaded success. - * If the entering animation has not set(i.e. 0), here will load the default entering - * animation for it. - * - * @param animationInfo The information of customize animation, which can be set from - * {@link Activity#overrideActivityTransition} and/or - * {@link LayoutParams#windowAnimations} - */ - AnimationLoadResult loadAll(BackNavigationInfo.CustomAnimationInfo animationInfo) { - if (animationInfo.getPackageName().isEmpty()) { - return null; - } - final Animation close = loadAnimation(animationInfo, false); - if (close == null) { - return null; - } - final Animation open = loadAnimation(animationInfo, true); - AnimationLoadResult result = new AnimationLoadResult(); - result.mCloseAnimation = close; - result.mEnterAnimation = open; - result.mBackgroundColor = animationInfo.getCustomBackground(); - return result; - } - - /** - * Load enter or exit animation from CustomAnimationInfo - * @param animationInfo The information for customize animation. - * @param enterAnimation true when load for enter animation, false for exit animation. - * @return Loaded animation. - */ - @Nullable - Animation loadAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo, - boolean enterAnimation) { - Animation a = null; - // Activity#overrideActivityTransition has higher priority than windowAnimations - // Try to get animation from Activity#overrideActivityTransition - if ((enterAnimation && animationInfo.getCustomEnterAnim() != 0) - || (!enterAnimation && animationInfo.getCustomExitAnim() != 0)) { - a = mTransitionAnimation.loadAppTransitionAnimation( - animationInfo.getPackageName(), - enterAnimation ? animationInfo.getCustomEnterAnim() - : animationInfo.getCustomExitAnim()); - } else if (animationInfo.getWindowAnimations() != 0) { - // try to get animation from LayoutParams#windowAnimations - a = mTransitionAnimation.loadAnimationAttr(animationInfo.getPackageName(), - animationInfo.getWindowAnimations(), enterAnimation - ? R.styleable.WindowAnimation_activityCloseEnterAnimation - : R.styleable.WindowAnimation_activityCloseExitAnimation, - false /* translucent */); - } - // Only allow to load default animation for opening target. - if (a == null && enterAnimation) { - a = loadDefaultOpenAnimation(); - } - if (a != null) { - ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a); - } else { - ProtoLog.e(WM_SHELL_BACK_PREVIEW, "No custom animation loaded"); - } - return a; - } - - private Animation loadDefaultOpenAnimation() { - return mTransitionAnimation.loadDefaultAnimationAttr( - R.styleable.WindowAnimation_activityCloseEnterAnimation, - false /* translucent */); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt new file mode 100644 index 000000000000..f33c5b9bd183 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.wm.shell.back + +import android.content.Context +import android.view.Choreographer +import android.view.SurfaceControl +import com.android.wm.shell.R +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.animation.Interpolators +import com.android.wm.shell.shared.annotations.ShellMainThread +import javax.inject.Inject +import kotlin.math.max + +/** Class that defines cross-activity animation. */ +@ShellMainThread +class DefaultCrossActivityBackAnimation +@Inject +constructor( + context: Context, + background: BackAnimationBackground, + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer +) : + CrossActivityBackAnimation( + context, + background, + rootTaskDisplayAreaOrganizer, + SurfaceControl.Transaction(), + Choreographer.getInstance() + ) { + + private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN + private val enteringStartOffset = + context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset) + override val allowEnteringYShift = true + + override fun preparePreCommitEnteringRectMovement() { + // the entering target starts 96dp to the left of the screen edge... + startEnteringRect.set(startClosingRect) + startEnteringRect.offset(-enteringStartOffset, 0f) + // ...and gets scaled in sync with the closing target + targetEnteringRect.set(startEnteringRect) + targetEnteringRect.scaleCentered(MAX_SCALE) + } + + override fun onGestureCommitted() { + // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current + // coordinate of the gesture driven phase. Let's update the start and target rects and kick + // off the animator in the superclass + startClosingRect.set(currentClosingRect) + startEnteringRect.set(currentEnteringRect) + targetEnteringRect.set(backAnimRect) + targetClosingRect.set(backAnimRect) + targetClosingRect.offset(currentClosingRect.left + enteringStartOffset, 0f) + super.onGestureCommitted() + } + + override fun onPostCommitProgress(linearProgress: Float) { + super.onPostCommitProgress(linearProgress) + val closingAlpha = max(1f - linearProgress * 2, 0f) + val progress = postCommitInterpolator.getInterpolation(linearProgress) + currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress) + applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha) + currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress) + applyTransform(enteringTarget?.leash, currentEnteringRect, 1f) + applyTransaction() + } +} 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 edd5935b508d..42a4ab2e352d 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 @@ -87,6 +87,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.statusbar.IStatusBarService; import com.android.launcher3.icons.BubbleIconFactory; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -170,6 +171,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; @@ -1162,16 +1165,52 @@ public class BubbleController implements ConfigurationChangeListener, } /** - * Update expanded state when a single bubble is dragged in Launcher. + * A bubble is being dragged in Launcher. * Will be called only when bubble bar is expanded. - * @param bubbleKey key of the bubble to collapse/expand - * @param isBeingDragged whether the bubble is being dragged + * + * @param bubbleKey key of the bubble being dragged + */ + public void startBubbleDrag(String bubbleKey) { + if (mBubbleData.getSelectedBubble() != null) { + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ false); + } + 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); + } + } + + /** + * A bubble is no longer being dragged in Launcher. And was released in given location. + * Will be called only when bubble bar is expanded. + * + * @param location location where bubble was released + * @param topOnScreen top coordinate of the bubble bar on the screen after release */ - public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) { - if (mBubbleData.getSelectedBubble() != null - && mBubbleData.getSelectedBubble().getKey().equals(bubbleKey)) { - // Should collapse/expand only if equals to selected bubble. - mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ !isBeingDragged); + public void stopBubbleDrag(BubbleBarLocation location, int topOnScreen) { + mBubblePositioner.setBubbleBarLocation(location); + mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen); + if (mBubbleData.getSelectedBubble() != null) { + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true); + } + } + + /** + * A bubble was dragged and is released in dismiss target in Launcher. + * + * @param bubbleKey key of the bubble being dragged to dismiss target + */ + public void dragBubbleToDismiss(String bubbleKey) { + String selectedBubbleKey = mBubbleData.getSelectedBubbleKey(); + removeBubble(bubbleKey, Bubbles.DISMISS_USER_GESTURE); + if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) { + // We did not remove the selected bubble. Expand it again + mBubbleBarViewCallback.expansionChanged(/* isExpanded = */ true); } } @@ -1211,8 +1250,8 @@ public class BubbleController implements ConfigurationChangeListener, * <p>This is used by external callers (launcher). */ @VisibleForTesting - public void expandStackAndSelectBubbleFromLauncher(String key, Rect bubbleBarBounds) { - mBubblePositioner.setBubbleBarBounds(bubbleBarBounds); + public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) { + mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen); if (BubbleOverflow.KEY.equals(key)) { mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow()); @@ -1351,28 +1390,32 @@ public class BubbleController implements ConfigurationChangeListener, } String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user); - Log.i(TAG, "showOrHideAppBubble, key= " + appBubbleKey + " stackVisibility= " - + (mStackView != null ? mStackView.getVisibility() : " null ") - + " statusBarShade=" + mIsStatusBarShade); PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier()); - if (!isResizableActivity(intent, packageManager, appBubbleKey)) return; + if (!isResizableActivity(intent, packageManager, appBubbleKey)) return; // logs errors Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(appBubbleKey); + ProtoLog.d(WM_SHELL_BUBBLES, + "showOrHideAppBubble, key=%s existingAppBubble=%s stackVisibility=%s " + + "statusBarShade=%s", + appBubbleKey, existingAppBubble, + (mStackView != null ? mStackView.getVisibility() : "null"), + mIsStatusBarShade); + if (existingAppBubble != null) { BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); if (isStackExpanded()) { if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) { + ProtoLog.d(WM_SHELL_BUBBLES, "collapseStack for %s", appBubbleKey); // App bubble is expanded, lets collapse - Log.i(TAG, " showOrHideAppBubble, selected bubble is app bubble, collapsing"); collapseStack(); } else { + ProtoLog.d(WM_SHELL_BUBBLES, "setSelected for %s", appBubbleKey); // App bubble is not selected, select it - Log.i(TAG, " showOrHideAppBubble, expanded, selecting existing app bubble"); mBubbleData.setSelectedBubble(existingAppBubble); } } else { + ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleAndExpandStack %s", appBubbleKey); // App bubble is not selected, select it & expand - Log.i(TAG, " showOrHideAppBubble, expand and select existing app bubble"); mBubbleData.setSelectedBubbleAndExpandStack(existingAppBubble); } } else { @@ -1380,13 +1423,12 @@ public class BubbleController implements ConfigurationChangeListener, Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey); if (b != null) { // It's in the overflow, so remove it & reinflate - Log.i(TAG, " showOrHideAppBubble, expanding app bubble from overflow"); - mBubbleData.removeOverflowBubble(b); + mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL); } else { // App bubble does not exist, lets add and expand it - Log.i(TAG, " showOrHideAppBubble, creating and expanding app bubble"); b = Bubble.createAppBubble(intent, user, icon, mMainExecutor); } + ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", appBubbleKey); b.setShouldAutoExpand(true); inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); } @@ -1824,6 +1866,15 @@ public class BubbleController implements ConfigurationChangeListener, } } + + @Override + public void bubbleOverflowChanged(boolean hasBubbles) { + if (Flags.enableOptionalBubbleOverflow()) { + if (mStackView != null) { + mStackView.showOverflow(hasBubbles); + } + } + } }; /** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */ @@ -1856,6 +1907,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 @@ -1894,7 +1950,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", @@ -1903,13 +1959,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(); @@ -2306,16 +2366,9 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void showBubble(String key, Rect bubbleBarBounds) { - mMainExecutor.execute( - () -> mController.expandStackAndSelectBubbleFromLauncher( - key, bubbleBarBounds)); - } - - @Override - public void removeBubble(String key) { + public void showBubble(String key, int topOnScreen) { mMainExecutor.execute( - () -> mController.removeBubble(key, Bubbles.DISMISS_USER_GESTURE)); + () -> mController.expandStackAndSelectBubbleFromLauncher(key, topOnScreen)); } @Override @@ -2329,8 +2382,18 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) { - mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged)); + public void startBubbleDrag(String bubbleKey) { + mMainExecutor.execute(() -> mController.startBubbleDrag(bubbleKey)); + } + + @Override + public void stopBubbleDrag(BubbleBarLocation location, int topOnScreen) { + mMainExecutor.execute(() -> mController.stopBubbleDrag(location, topOnScreen)); + } + + @Override + public void dragBubbleToDismiss(String key) { + mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key)); } @Override @@ -2346,8 +2409,11 @@ public class BubbleController implements ConfigurationChangeListener, } @Override - public void setBubbleBarBounds(Rect bubbleBarBounds) { - mMainExecutor.execute(() -> mBubblePositioner.setBubbleBarBounds(bubbleBarBounds)); + public void updateBubbleBarTopOnScreen(int topOnScreen) { + mMainExecutor.execute(() -> { + mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen); + if (mLayerView != null) mLayerView.updateExpandedView(); + }); } } @@ -2659,6 +2725,15 @@ public class BubbleController implements ConfigurationChangeListener, () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged( sensitiveNotificationProtectionActive)); } + + @Override + public boolean canShowBubbleNotification() { + // in bubble bar mode, when the IME is visible we can't animate new bubbles. + if (BubbleController.this.isShowingAsBubbleBar()) { + return !BubbleController.this.mBubblePositioner.getIsImeVisible(); + } + return true; + } } /** 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..26483c8428c6 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; } @@ -323,6 +327,14 @@ public class BubbleData { return mSelectedBubble; } + /** + * Returns the key of the selected bubble, or null if no bubble is selected. + */ + @Nullable + public String getSelectedBubbleKey() { + return mSelectedBubble != null ? mSelectedBubble.getKey() : null; + } + public BubbleOverflow getOverflow() { return mOverflow; } @@ -410,6 +422,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 +512,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 +685,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 +694,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 +794,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; @@ -1231,9 +1236,7 @@ public class BubbleData { public void dump(PrintWriter pw) { pw.println("BubbleData state:"); pw.print(" selected: "); - pw.println(mSelectedBubble != null - ? mSelectedBubble.getKey() - : "null"); + pw.println(getSelectedBubbleKey()); pw.print(" expanded: "); pw.println(mExpanded); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java index 633b01bde4ca..18e04d14c71b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java @@ -44,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ContrastColorUtil; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import java.util.ArrayList; @@ -195,7 +196,9 @@ public class BubbleOverflowContainerView extends LinearLayout { } void updateEmptyStateVisibility() { - mEmptyState.setVisibility(mOverflowBubbles.isEmpty() ? View.VISIBLE : View.GONE); + boolean showEmptyState = mOverflowBubbles.isEmpty() + && !Flags.enableOptionalBubbleOverflow(); + mEmptyState.setVisibility(showEmptyState ? View.VISIBLE : View.GONE); mRecyclerView.setVisibility(mOverflowBubbles.isEmpty() ? View.GONE : View.VISIBLE); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java index 14c3a0701c83..1e482cac0b46 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java @@ -76,6 +76,7 @@ public class BubblePositioner { private int mBubblePaddingTop; private int mBubbleOffscreenAmount; private int mStackOffset; + private int mBubbleElevation; private int mExpandedViewMinHeight; private int mExpandedViewLargeScreenWidth; @@ -97,7 +98,7 @@ public class BubblePositioner { private boolean mShowingInBubbleBar; private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT; - private final Rect mBubbleBarBounds = new Rect(); + private int mBubbleBarTopOnScreen; public BubblePositioner(Context context, WindowManager windowManager) { mContext = context; @@ -147,6 +148,7 @@ public class BubblePositioner { mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top); mBubbleOffscreenAmount = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen); mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset); + mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); if (mShowingInBubbleBar) { mExpandedViewLargeScreenWidth = Math.min( @@ -322,6 +324,11 @@ public class BubblePositioner { return 0; } + /** Returns whether the IME is visible. */ + public boolean getIsImeVisible() { + return mImeVisible; + } + /** Sets whether the IME is visible. **/ public void setImeVisible(boolean visible, int height) { mImeVisible = visible; @@ -662,6 +669,29 @@ public class BubblePositioner { } /** + * Returns the z translation a specific bubble should use. When expanded we keep a slight + * translation to ensure proper ordering when animating to / from collapsed state. When + * collapsed, only the top two bubbles appear so only their shadows show. + */ + public float getZTranslation(int index, boolean isOverflow, boolean isExpanded) { + if (isOverflow) { + return 0f; // overflow is lowest + } + return isExpanded + // When expanded use minimal amount to keep order + ? getMaxBubbles() - index + // When collapsed, only the top two bubbles have elevation + : index < NUM_VISIBLE_WHEN_RESTING + ? (getMaxBubbles() * mBubbleElevation) - index + : 0; + } + + /** The elevation to use for bubble UI elements. */ + public int getBubbleElevation() { + return mBubbleElevation; + } + + /** * @return whether the stack is considered on the left side of the screen. */ public boolean isStackOnLeft(PointF currentStackPosition) { @@ -816,17 +846,17 @@ public class BubblePositioner { } /** - * Sets the position of the bubble bar in display coordinates. + * Set top coordinate of bubble bar on screen */ - public void setBubbleBarBounds(Rect bubbleBarBounds) { - mBubbleBarBounds.set(bubbleBarBounds); + public void setBubbleBarTopOnScreen(int topOnScreen) { + mBubbleBarTopOnScreen = topOnScreen; } /** - * Returns the display coordinates of the bubble bar. + * Returns the top coordinate of bubble bar on screen */ - public Rect getBubbleBarBounds() { - return mBubbleBarBounds; + public int getBubbleBarTopOnScreen() { + return mBubbleBarTopOnScreen; } /** @@ -878,7 +908,7 @@ public class BubblePositioner { /** The bottom position of the expanded view when showing above the bubble bar. */ public int getExpandedViewBottomForBubbleBar() { - return mBubbleBarBounds.top - mExpandedViewPadding; + return mBubbleBarTopOnScreen - mExpandedViewPadding; } /** 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 dcc536b5b043..9fabd4247670 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 @@ -80,6 +80,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.FrameworkStatsLog; +import com.android.wm.shell.Flags; import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener; @@ -536,7 +537,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 @@ -670,7 +672,7 @@ public class BubbleStackView extends FrameLayout // First, see if the magnetized object consumes the event - if so, we shouldn't move the // bubble since it's stuck to the target. if (!passEventToMagnetizedObject(ev)) { - updateBubbleShadows(true /* showForAllBubbles */); + updateBubbleShadows(true /* isExpanded */); if (mBubbleData.isExpanded()) { mExpandedAnimationController.dragBubbleOut( v, viewInitialX + dx, viewInitialY + dy); @@ -719,7 +721,7 @@ public class BubbleStackView extends FrameLayout mDismissView.hide(); } - mIsDraggingStack = false; + onDraggingEnded(); // Hide the stack after a delay, if needed. updateTemporarilyInvisibleAnimation(false /* hideImmediately */); @@ -862,6 +864,7 @@ public class BubbleStackView extends FrameLayout } }; + private boolean mShowingOverflow; private BubbleOverflow mBubbleOverflow; private StackEducationView mStackEduView; private StackEducationView.Manager mStackEducationViewManager; @@ -890,18 +893,17 @@ public class BubbleStackView extends FrameLayout mMainExecutor = mainExecutor; mManager = bubbleStackViewManager; + mPositioner = bubblePositioner; mBubbleData = data; mSysuiProxyProvider = sysuiProxyProvider; Resources res = getResources(); mBubbleSize = res.getDimensionPixelSize(R.dimen.bubble_size); - mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); + mBubbleElevation = mPositioner.getBubbleElevation(); mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding); mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); - int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation); - mPositioner = bubblePositioner; final TypedArray ta = mContext.obtainStyledAttributes( new int[]{android.R.attr.dialogCornerRadius}); @@ -934,12 +936,12 @@ public class BubbleStackView extends FrameLayout mBubbleContainer = new PhysicsAnimationLayout(context); mBubbleContainer.setActiveController(mStackAnimationController); - mBubbleContainer.setElevation(elevation); + mBubbleContainer.setElevation(mBubbleElevation); mBubbleContainer.setClipChildren(false); addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); mExpandedViewContainer = new FrameLayout(context); - mExpandedViewContainer.setElevation(elevation); + mExpandedViewContainer.setElevation(mBubbleElevation); mExpandedViewContainer.setClipChildren(false); addView(mExpandedViewContainer); @@ -992,18 +994,12 @@ public class BubbleStackView extends FrameLayout mBubbleOverflow = mBubbleData.getOverflow(); - resetOverflowView(); - mBubbleContainer.addView(mBubbleOverflow.getIconView(), - mBubbleContainer.getChildCount() /* index */, - new FrameLayout.LayoutParams(mPositioner.getBubbleSize(), - mPositioner.getBubbleSize())); - updateOverflow(); - mBubbleOverflow.getIconView().setOnClickListener((View v) -> { - mBubbleData.setShowingOverflow(true); - mBubbleData.setSelectedBubble(mBubbleOverflow); - mBubbleData.setExpanded(true); - }); - + if (Flags.enableOptionalBubbleOverflow()) { + showOverflow(mBubbleData.hasOverflowBubbles()); + } else { + mShowingOverflow = true; // if the flags not on this is always true + setUpOverflow(); + } mScrim = new View(getContext()); mScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); mScrim.setBackgroundDrawable(new ColorDrawable( @@ -1096,6 +1092,7 @@ public class BubbleStackView extends FrameLayout } else { maybeShowStackEdu(); } + onDraggingEnded(); }); animate() @@ -1153,6 +1150,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. * @@ -1211,6 +1216,19 @@ public class BubbleStackView extends FrameLayout } }; + private void setUpOverflow() { + resetOverflowView(); + mBubbleContainer.addView(mBubbleOverflow.getIconView(), + mBubbleContainer.getChildCount() /* index */, + new FrameLayout.LayoutParams(mBubbleSize, mBubbleSize)); + updateOverflow(); + mBubbleOverflow.getIconView().setOnClickListener((View v) -> { + mBubbleData.setShowingOverflow(true); + mBubbleData.setSelectedBubble(mBubbleOverflow); + mBubbleData.setExpanded(true); + }); + } + private void setUpDismissView() { if (mDismissView != null) { removeView(mDismissView); @@ -1449,24 +1467,56 @@ public class BubbleStackView extends FrameLayout b.getExpandedView().updateFontSize(); } } - if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) { + if (mShowingOverflow && mBubbleOverflow != null + && mBubbleOverflow.getExpandedView() != null) { mBubbleOverflow.getExpandedView().updateFontSize(); } } void updateLocale() { - if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) { + if (mShowingOverflow && mBubbleOverflow != null + && mBubbleOverflow.getExpandedView() != null) { mBubbleOverflow.getExpandedView().updateLocale(); } } private void updateOverflow() { mBubbleOverflow.update(); - mBubbleContainer.reorderView(mBubbleOverflow.getIconView(), - mBubbleContainer.getChildCount() - 1 /* index */); + if (mShowingOverflow) { + mBubbleContainer.reorderView(mBubbleOverflow.getIconView(), + mBubbleContainer.getChildCount() - 1 /* index */); + } updateOverflowVisibility(); } + private void updateOverflowVisibility() { + mBubbleOverflow.setVisible(mShowingOverflow + && (mIsExpanded || mBubbleData.isShowingOverflow()) + ? VISIBLE + : GONE); + } + + private void updateOverflowDotVisibility(boolean expanding) { + if (mShowingOverflow && mBubbleOverflow.showDot()) { + mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> { + mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE); + }); + } + } + + /** Sets whether the overflow should be visible or not. */ + public void showOverflow(boolean showOverflow) { + if (!Flags.enableOptionalBubbleOverflow()) return; + if (mShowingOverflow != showOverflow) { + mShowingOverflow = showOverflow; + if (showOverflow) { + setUpOverflow(); + } else if (mBubbleOverflow != null) { + resetOverflowView(); + } + } + } + /** * Handle theme changes. */ @@ -1526,7 +1576,10 @@ public class BubbleStackView extends FrameLayout b.getExpandedView().updateDimensions(); } } - mBubbleOverflow.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize)); + if (mShowingOverflow) { + mBubbleOverflow.getIconView().setLayoutParams( + new LayoutParams(mBubbleSize, mBubbleSize)); + } mExpandedAnimationController.updateResources(); mStackAnimationController.updateResources(); mDismissView.updateResources(); @@ -1690,7 +1743,7 @@ public class BubbleStackView extends FrameLayout bubble.getIconView().setContentDescription(getResources().getString( R.string.bubble_content_description_single, titleStr, appName)); } else { - final int moreCount = mBubbleContainer.getChildCount() - 1; + final int moreCount = getBubbleCount(); bubble.getIconView().setContentDescription(getResources().getString( R.string.bubble_content_description_stack, titleStr, appName, moreCount)); @@ -1743,7 +1796,8 @@ public class BubbleStackView extends FrameLayout View bubbleOverflowIconView = mBubbleOverflow != null ? mBubbleOverflow.getIconView() : null; - if (bubbleOverflowIconView != null && !mBubbleData.getBubbles().isEmpty()) { + if (mShowingOverflow && bubbleOverflowIconView != null + && !mBubbleData.getBubbles().isEmpty()) { Bubble lastBubble = mBubbleData.getBubbles().get(mBubbleData.getBubbles().size() - 1); View lastBubbleIconView = lastBubble.getIconView(); @@ -1861,7 +1915,7 @@ public class BubbleStackView extends FrameLayout bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */); bubble.getIconView().setOnClickListener(mBubbleClickListener); bubble.getIconView().setOnTouchListener(mBubbleTouchListener); - updateBubbleShadows(false /* showForAllBubbles */); + updateBubbleShadows(mIsExpanded); animateInFlyoutForBubble(bubble); requestUpdate(); logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED); @@ -1919,20 +1973,6 @@ public class BubbleStackView extends FrameLayout } } - private void updateOverflowVisibility() { - mBubbleOverflow.setVisible((mIsExpanded || mBubbleData.isShowingOverflow()) - ? VISIBLE - : GONE); - } - - private void updateOverflowDotVisibility(boolean expanding) { - if (mBubbleOverflow.showDot()) { - mBubbleOverflow.getIconView().animateDotScale(expanding ? 1 : 0f, () -> { - mBubbleOverflow.setVisible(expanding ? VISIBLE : GONE); - }); - } - } - // via BubbleData.Listener void updateBubble(Bubble bubble) { animateInFlyoutForBubble(bubble); @@ -1964,7 +2004,7 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded || isExpansionAnimating()) { reorder.run(); updateBadges(false /* setBadgeForCollapsedStack */); - updateZOrder(); + updateBubbleShadows(true /* isExpanded */); } else { List<View> bubbleViews = bubbles.stream() .map(b -> b.getIconView()).collect(Collectors.toList()); @@ -2207,7 +2247,7 @@ public class BubbleStackView extends FrameLayout mBubbleContainer.addView(bubble.getIconView(), index, new LayoutParams(mPositioner.getBubbleSize(), mPositioner.getBubbleSize())); - updateBubbleShadows(false /* showForAllBubbles */); + updateBubbleShadows(mIsExpanded); requestUpdate(); } } @@ -2340,9 +2380,9 @@ public class BubbleStackView extends FrameLayout beforeExpandedViewAnimation(); showScrim(true, null /* runnable */); - updateZOrder(); - updateBadges(false /* setBadgeForCollapsedStack */); + updateBubbleShadows(mIsExpanded); mBubbleContainer.setActiveController(mExpandedAnimationController); + updateBadges(false /* setBadgeForCollapsedStack */); updateOverflowVisibility(); updatePointerPosition(false /* forIme */); mExpandedAnimationController.expandFromStack(() -> { @@ -2493,6 +2533,7 @@ public class BubbleStackView extends FrameLayout () -> { mBubbleContainer.setActiveController(mStackAnimationController); updateOverflowVisibility(); + animateShadows(); }); final Runnable after = () -> { @@ -2503,7 +2544,6 @@ public class BubbleStackView extends FrameLayout mManageEduView.hide(); } - updateZOrder(); updateBadges(true /* setBadgeForCollapsedStack */); afterExpandedViewAnimation(); if (previouslySelected != null) { @@ -3338,19 +3378,23 @@ public class BubbleStackView extends FrameLayout * Updates whether each of the bubbles should show shadows. When collapsed & resting, only the * visible bubbles (top 2) will show a shadow. When the stack is being dragged, everything * shows a shadow. When an individual bubble is dragged out, it should show a shadow. - */ - private void updateBubbleShadows(boolean showForAllBubbles) { - int bubbleCount = getBubbleCount(); - for (int i = 0; i < bubbleCount; i++) { - final float z = (mPositioner.getMaxBubbles() * mBubbleElevation) - i; - BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i); - boolean isDraggedOut = mMagnetizedObject != null + * The bubble overflow is a special case and never has a shadow as it's ordered below the + * rest of the bubbles and isn't visible unless the stack is expanded. + * + * @param isExpanded whether the stack will be expanded or not when the shadows are applied. + */ + private void updateBubbleShadows(boolean isExpanded) { + final int childCount = mBubbleContainer.getChildCount(); + for (int i = 0; i < childCount; i++) { + final BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i); + final boolean isOverflow = BubbleOverflow.KEY.equals(bv.getKey()); + final boolean isDraggedOut = mMagnetizedObject != null && mMagnetizedObject.getUnderlyingObject().equals(bv); - if (showForAllBubbles || isDraggedOut) { - bv.setZ(z); + if (isDraggedOut) { + // If it's dragged out, it's above all the other bubbles + bv.setZ((mPositioner.getMaxBubbles() * mBubbleElevation) + 1); } else { - final float tz = i < NUM_VISIBLE_WHEN_RESTING ? z : 0f; - bv.setZ(tz); + bv.setZ(mPositioner.getZTranslation(i, isOverflow, isExpanded)); } } } @@ -3371,16 +3415,6 @@ public class BubbleStackView extends FrameLayout } } - private void updateZOrder() { - int bubbleCount = getBubbleCount(); - for (int i = 0; i < bubbleCount; i++) { - BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i); - bv.setZ(i < NUM_VISIBLE_WHEN_RESTING - ? (mPositioner.getMaxBubbles() * mBubbleElevation) - i - : 0f); - } - } - private void updateBadges(boolean setBadgeForCollapsedStack) { int bubbleCount = getBubbleCount(); for (int i = 0; i < bubbleCount; i++) { @@ -3425,8 +3459,9 @@ public class BubbleStackView extends FrameLayout * @return the number of bubbles in the stack view. */ public int getBubbleCount() { - // Subtract 1 for the overflow button that is always in the bubble container. - return mBubbleContainer.getChildCount() - 1; + final int childCount = mBubbleContainer.getChildCount(); + // Subtract 1 for the overflow button if it's showing. + return mShowingOverflow ? childCount - 1 : childCount; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 322088b17e63..1d053f9aab35 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -297,6 +297,15 @@ public interface Bubbles { boolean sensitiveNotificationProtectionActive); /** + * Determines whether Bubbles can show notifications. + * + * <p>Normally bubble notifications are shown by Bubbles, but in some cases the bubble + * notification is suppressed and should be shown by the Notifications pipeline as regular + * notifications. + */ + boolean canShowBubbleNotification(); + + /** * A listener to be notified of bubble state changes, used by launcher to render bubbles in * its process. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index c9f0f0d61713..1db556c04180 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -31,19 +31,21 @@ interface IBubbles { oneway void unregisterBubbleListener(in IBubblesListener listener) = 2; - oneway void showBubble(in String key, in Rect bubbleBarBounds) = 3; + oneway void showBubble(in String key, in int topOnScreen) = 3; - oneway void removeBubble(in String key) = 4; + oneway void dragBubbleToDismiss(in String key) = 4; oneway void removeAllBubbles() = 5; oneway void collapseBubbles() = 6; - oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7; + oneway void startBubbleDrag(in String key) = 7; oneway void showUserEducation(in int positionX, in int positionY) = 8; oneway void setBubbleBarLocation(in BubbleBarLocation location) = 9; - oneway void setBubbleBarBounds(in Rect bubbleBarBounds) = 10; + oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10; + + oneway void stopBubbleDrag(in BubbleBarLocation location, in int topOnScreen) = 11; }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java index 9440e9835cdf..f925eaef2c77 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java @@ -356,7 +356,6 @@ public class ExpandedAnimationController MagnetizedObject.MagnetListener listener) { mLayout.cancelAnimationsOnView(bubble); - bubble.setTranslationZ(Short.MAX_VALUE); mMagnetizedBubbleDraggingOut = new MagnetizedObject<View>( mLayout.getContext(), bubble, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) { @@ -460,6 +459,7 @@ public class ExpandedAnimationController /** * Snaps a bubble back to its position within the bubble row, and animates the rest of the * bubbles to accommodate it if it was previously dragged out past the threshold. + * Only happens while the stack is expanded. */ public void snapBubbleBack(View bubbleView, float velX, float velY) { if (mLayout == null) { @@ -467,8 +467,12 @@ public class ExpandedAnimationController } final int index = mLayout.indexOfChild(bubbleView); final PointF p = mPositioner.getExpandedBubbleXY(index, mBubbleStackView.getState()); + // overflow is not draggable so it's never the overflow + final float zTranslation = mPositioner.getZTranslation(index, + false /* isOverflow */, + true /* isExpanded */); animationForChildAtIndex(index) - .position(p.x, p.y, /* translationZ= */ 0f) + .position(p.x, p.y, zTranslation) .withPositionStartVelocities(velX, velY) .start(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java index 45ad6319bbf8..8e58db198b13 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java @@ -237,12 +237,10 @@ public class BubbleBarAnimationHelper { private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) { // Set the pivot point for the scale, so the view animates out from the bubble bar. - Rect bubbleBarBounds = mPositioner.getBubbleBarBounds(); - matrix.setScale( - scale, - scale, - bubbleBarBounds.centerX(), - bubbleBarBounds.top); + Rect availableRect = mPositioner.getAvailableRect(); + float pivotX = mPositioner.isBubbleBarOnLeft() ? availableRect.left : availableRect.right; + float pivotY = mPositioner.getBubbleBarTopOnScreen(); + matrix.setScale(scale, scale, pivotX, pivotY); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java index 2b7a0706b4de..d54a6b002e43 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarHandleView.java @@ -37,15 +37,11 @@ import com.android.wm.shell.R; */ public class BubbleBarHandleView extends View { private static final long COLOR_CHANGE_DURATION = 120; - - // The handle view is currently rendered as 3 evenly spaced dots. - private int mDotSize; - private int mDotSpacing; // Path used to draw the dots private final Path mPath = new Path(); - private @ColorInt int mHandleLightColor; - private @ColorInt int mHandleDarkColor; + private final @ColorInt int mHandleLightColor; + private final @ColorInt int mHandleDarkColor; private @Nullable ObjectAnimator mColorChangeAnim; public BubbleBarHandleView(Context context) { @@ -63,10 +59,8 @@ public class BubbleBarHandleView extends View { public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); - mDotSize = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_caption_dot_size); - mDotSpacing = getResources().getDimensionPixelSize( - R.dimen.bubble_bar_expanded_view_caption_dot_spacing); + final int handleHeight = getResources().getDimensionPixelSize( + R.dimen.bubble_bar_expanded_view_handle_height); mHandleLightColor = ContextCompat.getColor(getContext(), R.color.bubble_bar_expanded_view_handle_light); mHandleDarkColor = ContextCompat.getColor(getContext(), @@ -76,27 +70,13 @@ public class BubbleBarHandleView extends View { setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - final int handleCenterX = view.getWidth() / 2; final int handleCenterY = view.getHeight() / 2; - final int handleTotalWidth = mDotSize * 3 + mDotSpacing * 2; - final int handleLeft = handleCenterX - handleTotalWidth / 2; - final int handleTop = handleCenterY - mDotSize / 2; - final int handleBottom = handleTop + mDotSize; - RectF dot1 = new RectF( - handleLeft, handleTop, - handleLeft + mDotSize, handleBottom); - RectF dot2 = new RectF( - dot1.right + mDotSpacing, handleTop, - dot1.right + mDotSpacing + mDotSize, handleBottom - ); - RectF dot3 = new RectF( - dot2.right + mDotSpacing, handleTop, - dot2.right + mDotSpacing + mDotSize, handleBottom - ); + final int handleTop = handleCenterY - handleHeight / 2; + final int handleBottom = handleTop + handleHeight; + final int radius = handleHeight / 2; + RectF handle = new RectF(/* left = */ 0, handleTop, view.getWidth(), handleBottom); mPath.reset(); - mPath.addOval(dot1, Path.Direction.CW); - mPath.addOval(dot2, Path.Direction.CW); - mPath.addOval(dot3, Path.Direction.CW); + mPath.addRoundRect(handle, radius, radius, Path.Direction.CW); outline.setPath(mPath); } }); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index a351cef223b5..123cc7e9d488 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -356,7 +356,7 @@ public class BubbleBarLayerView extends FrameLayout } /** Updates the expanded view size and position. */ - private void updateExpandedView() { + public void updateExpandedView() { if (mExpandedView == null || mExpandedBubble == null) return; boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); mPositioner.getBubbleBarExpandedViewBounds(mPositioner.isBubbleBarOnLeft(), 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..579a7943829e 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,24 @@ 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( + "persist.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/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index a87116ea4670..2234041b8c9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -185,7 +185,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { nextTarget = snapAlgorithm.getDismissStartTarget(); } if (nextTarget != null) { - mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), nextTarget); + mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), nextTarget); return true; } return super.performAccessibilityAction(host, action, args); @@ -345,9 +345,9 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { mMoving = true; } if (mMoving) { - final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; + final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos; mLastDraggingPosition = position; - mSplitLayout.updateDivideBounds(position); + mSplitLayout.updateDividerBounds(position, true /* shouldUseParallaxEffect */); } break; case MotionEvent.ACTION_UP: @@ -363,7 +363,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { final float velocity = isLeftRightSplit ? mVelocityTracker.getXVelocity() : mVelocityTracker.getYVelocity(); - final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos; + final int position = mSplitLayout.getDividerPosition() + touchPos - mStartPos; final DividerSnapAlgorithm.SnapTarget snapTarget = mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */); mSplitLayout.snapToTarget(position, snapTarget); @@ -472,12 +472,12 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { mInteractive = interactive; mHideHandle = hideHandle; if (!mInteractive && mHideHandle && mMoving) { - final int position = mSplitLayout.getDividePosition(); - mSplitLayout.flingDividePosition( + final int position = mSplitLayout.getDividerPosition(); + mSplitLayout.flingDividerPosition( mLastDraggingPosition, position, mSplitLayout.FLING_RESIZE_DURATION, - () -> mSplitLayout.setDividePosition(position, true /* applyLayoutChange */)); + () -> mSplitLayout.setDividerPosition(position, true /* applyLayoutChange */)); mMoving = false; } releaseTouching(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 30eb8b5d2f05..de016d3ae400 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -31,7 +31,6 @@ import android.animation.ValueAnimator; import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; -import android.graphics.Color; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -57,7 +56,13 @@ import com.android.wm.shell.common.SurfaceUtils; import java.util.function.Consumer; /** - * Handles split decor like showing resizing hint for a specific split. + * Handles additional layers over a running task in a split pair, for example showing a veil with an + * app icon when the task is being resized (usually to hide weird layouts while the app is being + * stretched). One SplitDecorManager is initialized on each window. + * <br> + * Currently, we show a veil when: + * a) Task is resizing down from a fullscreen window. + * b) Task is being stretched past its original bounds. */ public class SplitDecorManager extends WindowlessWindowManager { private static final String TAG = SplitDecorManager.class.getSimpleName(); @@ -78,7 +83,11 @@ public class SplitDecorManager extends WindowlessWindowManager { private boolean mShown; private boolean mIsResizing; - private final Rect mOldBounds = new Rect(); + /** The original bounds of the main task, captured at the beginning of a resize transition. */ + private final Rect mOldMainBounds = new Rect(); + /** The original bounds of the side task, captured at the beginning of a resize transition. */ + private final Rect mOldSideBounds = new Rect(); + /** The current bounds of the main task, mid-resize. */ private final Rect mResizingBounds = new Rect(); private final Rect mTempRect = new Rect(); private ValueAnimator mFadeAnimator; @@ -184,29 +193,38 @@ public class SplitDecorManager extends WindowlessWindowManager { mResizingIconView = null; mIsResizing = false; mShown = false; - mOldBounds.setEmpty(); + mOldMainBounds.setEmpty(); + mOldSideBounds.setEmpty(); mResizingBounds.setEmpty(); } /** Showing resizing hint. */ public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY, - boolean immediately) { + boolean immediately, float[] veilColor) { if (mResizingIconView == null) { return; } if (!mIsResizing) { mIsResizing = true; - mOldBounds.set(newBounds); + mOldMainBounds.set(newBounds); + mOldSideBounds.set(sideBounds); } mResizingBounds.set(newBounds); mOffsetX = offsetX; mOffsetY = offsetY; - final boolean show = - newBounds.width() > mOldBounds.width() || newBounds.height() > mOldBounds.height(); - final boolean update = show != mShown; + // Show a veil when: + // a) Task is resizing down from a fullscreen window. + // b) Task is being stretched past its original bounds. + final boolean isResizingDownFromFullscreen = + mOldSideBounds.width() <= 1 || mOldSideBounds.height() <= 1; + final boolean isStretchingPastOriginalBounds = + newBounds.width() > mOldMainBounds.width() + || newBounds.height() > mOldMainBounds.height(); + final boolean showVeil = isResizingDownFromFullscreen || isStretchingPastOriginalBounds; + final boolean update = showVeil != mShown; if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) { // If we need to animate and animator still running, cancel it before we ensure both // background and icon surfaces are non null for next animation. @@ -216,18 +234,18 @@ public class SplitDecorManager extends WindowlessWindowManager { if (mBackgroundLeash == null) { mBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, RESIZING_BACKGROUND_SURFACE_NAME, mSurfaceSession); - t.setColor(mBackgroundLeash, getResizingBackgroundColor(resizingTask)) + t.setColor(mBackgroundLeash, veilColor) .setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1); } if (mGapBackgroundLeash == null && !immediately) { final boolean isLandscape = newBounds.height() == sideBounds.height(); - final int left = isLandscape ? mOldBounds.width() : 0; - final int top = isLandscape ? 0 : mOldBounds.height(); + final int left = isLandscape ? mOldMainBounds.width() : 0; + final int top = isLandscape ? 0 : mOldMainBounds.height(); mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash, GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession); // Fill up another side bounds area. - t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask)) + t.setColor(mGapBackgroundLeash, veilColor) .setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2) .setPosition(mGapBackgroundLeash, left, top) .setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height()); @@ -251,12 +269,12 @@ public class SplitDecorManager extends WindowlessWindowManager { if (update) { if (immediately) { - t.setVisibility(mBackgroundLeash, show); - t.setVisibility(mIconLeash, show); + t.setVisibility(mBackgroundLeash, showVeil); + t.setVisibility(mIconLeash, showVeil); } else { - startFadeAnimation(show, false, null); + startFadeAnimation(showVeil, false, null); } - mShown = show; + mShown = showVeil; } } @@ -309,7 +327,8 @@ public class SplitDecorManager extends WindowlessWindowManager { mIsResizing = false; mOffsetX = 0; mOffsetY = 0; - mOldBounds.setEmpty(); + mOldMainBounds.setEmpty(); + mOldSideBounds.setEmpty(); mResizingBounds.setEmpty(); if (mFadeAnimator != null && mFadeAnimator.isRunning()) { if (!mShown) { @@ -346,14 +365,14 @@ public class SplitDecorManager extends WindowlessWindowManager { /** Screenshot host leash and attach on it if meet some conditions */ public void screenshotIfNeeded(SurfaceControl.Transaction t) { - if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) { + if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) { if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { mScreenshotAnimator.cancel(); } else if (mScreenshot != null) { t.remove(mScreenshot); } - mTempRect.set(mOldBounds); + mTempRect.set(mOldMainBounds); mTempRect.offsetTo(0, 0); mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect, Integer.MAX_VALUE - 1); @@ -364,7 +383,7 @@ public class SplitDecorManager extends WindowlessWindowManager { public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) { if (screenshot == null || !screenshot.isValid()) return; - if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) { + if (!mShown && mIsResizing && !mOldMainBounds.equals(mResizingBounds)) { if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) { mScreenshotAnimator.cancel(); } else if (mScreenshot != null) { @@ -465,9 +484,4 @@ public class SplitDecorManager extends WindowlessWindowManager { mIcon = null; } } - - private static float[] getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { - final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); - return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).getComponents(); - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 6b2d544c192a..8331654839c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -78,7 +78,7 @@ import java.util.function.Consumer; /** * Records and handles layout of splits. Helps to calculate proper bounds when configuration or - * divide position changes. + * divider position changes. */ public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener { private static final String TAG = "SplitLayout"; @@ -278,7 +278,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return mSplitWindowManager == null ? null : mSplitWindowManager.getSurfaceControl(); } - int getDividePosition() { + int getDividerPosition() { return mDividerPosition; } @@ -489,20 +489,20 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void setDividerAtBorder(boolean start) { final int pos = start ? mDividerSnapAlgorithm.getDismissStartTarget().position : mDividerSnapAlgorithm.getDismissEndTarget().position; - setDividePosition(pos, false /* applyLayoutChange */); + setDividerPosition(pos, false /* applyLayoutChange */); } /** * Updates bounds with the passing position. Usually used to update recording bounds while * performing animation or dragging divider bar to resize the splits. */ - void updateDivideBounds(int position) { + void updateDividerBounds(int position, boolean shouldUseParallaxEffect) { updateBounds(position); mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x, - mSurfaceEffectPolicy.mParallaxOffset.y); + mSurfaceEffectPolicy.mParallaxOffset.y, shouldUseParallaxEffect); } - void setDividePosition(int position, boolean applyLayoutChange) { + void setDividerPosition(int position, boolean applyLayoutChange) { mDividerPosition = position; updateBounds(mDividerPosition); if (applyLayoutChange) { @@ -511,14 +511,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } /** - * Updates divide position and split bounds base on the ratio within root bounds. Falls back + * Updates divider position and split bounds base on the ratio within root bounds. Falls back * to middle position if the provided SnapTarget is not supported. */ public void setDivideRatio(@PersistentSnapPosition int snapPosition) { final DividerSnapAlgorithm.SnapTarget snapTarget = mDividerSnapAlgorithm.findSnapTarget( snapPosition); - setDividePosition(snapTarget != null + setDividerPosition(snapTarget != null ? snapTarget.position : mDividerSnapAlgorithm.getMiddleTarget().position, false /* applyLayoutChange */); @@ -546,24 +546,24 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } /** - * Sets new divide position and updates bounds correspondingly. Notifies listener if the new + * Sets new divider position and updates bounds correspondingly. Notifies listener if the new * target indicates dismissing split. */ public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) { switch (snapTarget.snapPosition) { case SNAP_TO_START_AND_DISMISS: - flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, + flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; case SNAP_TO_END_AND_DISMISS: - flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, + flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */, EXIT_REASON_DRAG_DIVIDER)); break; default: - flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, - () -> setDividePosition(snapTarget.position, true /* applyLayoutChange */)); + flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION, + () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */)); break; } } @@ -615,19 +615,19 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public void flingDividerToDismiss(boolean toEnd, int reason) { final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position : mDividerSnapAlgorithm.getDismissStartTarget().position; - flingDividePosition(getDividePosition(), target, FLING_EXIT_DURATION, + flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION, () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason)); } /** Fling divider from current position to center position. */ public void flingDividerToCenter() { final int pos = mDividerSnapAlgorithm.getMiddleTarget().position; - flingDividePosition(getDividePosition(), pos, FLING_ENTER_DURATION, - () -> setDividePosition(pos, true /* applyLayoutChange */)); + flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION, + () -> setDividerPosition(pos, true /* applyLayoutChange */)); } @VisibleForTesting - void flingDividePosition(int from, int to, int duration, + void flingDividerPosition(int from, int to, int duration, @Nullable Runnable flingFinishedCallback) { if (from == to) { if (flingFinishedCallback != null) { @@ -647,7 +647,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange .setDuration(duration); mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); mDividerFlingAnimator.addUpdateListener( - animation -> updateDivideBounds((int) animation.getAnimatedValue())); + animation -> updateDividerBounds( + (int) animation.getAnimatedValue(), false /* shouldUseParallaxEffect */) + ); mDividerFlingAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -897,7 +899,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange * @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl, * SurfaceControl, SurfaceControl, boolean) */ - void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY); + void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY, + boolean shouldUseParallaxEffect); /** * Calls when finish resizing the split bounds. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java index f9259e79472e..e8226051b672 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java @@ -16,8 +16,6 @@ package com.android.wm.shell.common.split; -import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED; - import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES; import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -26,25 +24,18 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import android.app.ActivityManager; import android.app.PendingIntent; -import android.content.ComponentName; import android.content.Intent; -import android.content.pm.LauncherApps; -import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Color; import android.graphics.Rect; -import android.os.UserHandle; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.util.ArrayUtils; import com.android.wm.shell.Flags; import com.android.wm.shell.ShellTaskOrganizer; -import java.util.Arrays; -import java.util.List; - /** Helper utility class for split screen components to use. */ public class SplitScreenUtils { /** Reverse the split position. */ @@ -137,4 +128,10 @@ public class SplitScreenUtils { return isLandscape; } } + + /** Returns the specified background color that matches a RunningTaskInfo. */ + public static Color getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { + final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); + return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java index cf3ad4299cea..713d04bce4e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java @@ -194,6 +194,10 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi return mHideSizeCompatRestartButtonTolerance; } + int getDefaultHideRestartButtonTolerance() { + return MAX_PERCENTAGE_VAL; + } + boolean getHasSeenLetterboxEducation(int userId) { return mLetterboxEduSharedPreferences .getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false); 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 4e5c2fa24f25..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 @@ -20,11 +20,11 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED; import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; +import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.AppCompatTaskInfo; import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.TaskInfo; import android.content.Context; @@ -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; @@ -219,14 +227,30 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { @VisibleForTesting boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) { - if (!Flags.allowHideScmButton()) { + // Always show button if display is phone sized. + if (!Flags.allowHideScmButton() || taskInfo.configuration.smallestScreenWidthDp + < LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP) { + return true; + } + + final int letterboxWidth = taskInfo.appCompatTaskInfo.topActivityLetterboxWidth; + final int letterboxHeight = taskInfo.appCompatTaskInfo.topActivityLetterboxHeight; + final Rect stableBounds = getTaskStableBounds(); + final int appWidth = stableBounds.width(); + final int appHeight = stableBounds.height(); + // App is floating, should always show restart button. + if (appWidth > letterboxWidth && appHeight > letterboxHeight) { return true; } - final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo; - final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds(); - final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth, - appCompatTaskInfo.topActivityLetterboxHeight); - final int taskArea = computeArea(taskBounds.width(), taskBounds.height()); + // If app fills the width of the display, don't show restart button (for landscape apps) + // if device has a custom tolerance value. + if (mHideScmTolerance != mCompatUIConfiguration.getDefaultHideRestartButtonTolerance() + && appWidth == letterboxWidth) { + return false; + } + + final int letterboxArea = letterboxWidth * letterboxHeight; + final int taskArea = appWidth * appHeight; if (letterboxArea == 0 || taskArea == 0) { return false; } @@ -234,13 +258,6 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract { return percentageAreaOfLetterboxInTask < mHideScmTolerance; } - private int computeArea(int width, int height) { - if (width == 0 || height == 0) { - return 0; - } - return width * height; - } - private void updateVisibilityOfViews() { if (mLayout == null) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 17121c8de428..991fbafed296 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -72,7 +72,6 @@ import com.android.wm.shell.compatui.CompatUIConfiguration; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.compatui.CompatUIShellCommandHandler; import com.android.wm.shell.desktopmode.DesktopMode; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; @@ -88,6 +87,7 @@ import com.android.wm.shell.performance.PerfHintController; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; @@ -891,13 +891,13 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static Optional<DesktopTasksController> providesDesktopTasksController( + static Optional<DesktopTasksController> providesDesktopTasksController(Context context, @DynamicOverride Optional<Lazy<DesktopTasksController>> desktopTasksController) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. return desktopTasksController.flatMap((lazy)-> { - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of(lazy.get()); } return Optional.empty(); @@ -910,13 +910,13 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides - static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository( + static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(Context context, @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) { // Use optional-of-lazy for the dependency that this provider relies on. // Lazy ensures that this provider will not be the cause the dependency is created // when it will not be returned due to the condition below. return desktopModeTaskRepository.flatMap((lazy)-> { - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.of(lazy.get()); } return Optional.empty(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index b574b8159307..4e9e8f97620c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -57,7 +57,6 @@ import com.android.wm.shell.dagger.back.ShellBackAnimationModule; import com.android.wm.shell.dagger.pip.PipModule; import com.android.wm.shell.desktopmode.DesktopModeEventLogger; import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksLimiter; @@ -77,6 +76,7 @@ import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.recents.RecentsTransitionHandler; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.annotations.ShellAnimationThread; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.shared.annotations.ShellMainThread; @@ -100,6 +100,7 @@ import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition; import com.android.wm.shell.unfold.qualifier.UnfoldTransition; import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; +import com.android.wm.shell.windowdecor.ResizeHandleSizeRepository; import com.android.wm.shell.windowdecor.WindowDecorViewModel; import dagger.Binds; @@ -220,8 +221,9 @@ public abstract class WMShellModule { SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { - if (DesktopModeStatus.isEnabled()) { + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + ResizeHandleSizeRepository resizeHandleSizeRepository) { + if (DesktopModeStatus.canEnterDesktopMode(context)) { return new DesktopModeWindowDecorViewModel( context, mainExecutor, @@ -237,7 +239,8 @@ public abstract class WMShellModule { syncQueue, transitions, desktopTasksController, - rootTaskDisplayAreaOrganizer); + rootTaskDisplayAreaOrganizer, + resizeHandleSizeRepository); } return new CaptionWindowDecorViewModel( context, @@ -247,7 +250,8 @@ public abstract class WMShellModule { displayController, rootTaskDisplayAreaOrganizer, syncQueue, - transitions); + transitions, + resizeHandleSizeRepository); } // @@ -278,8 +282,8 @@ public abstract class WMShellModule { ShellInit init = FreeformComponents.isFreeformEnabled(context) ? shellInit : null; - return new FreeformTaskListener(init, shellTaskOrganizer, desktopModeTaskRepository, - windowDecorViewModel); + return new FreeformTaskListener(context, init, shellTaskOrganizer, + desktopModeTaskRepository, windowDecorViewModel); } @WMSingleton @@ -529,16 +533,19 @@ public abstract class WMShellModule { exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, - recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter); + recentsTransitionHandler, multiInstanceHelper, + mainExecutor, desktopTasksLimiter); } @WMSingleton @Provides static Optional<DesktopTasksLimiter> provideDesktopTasksLimiter( + Context context, Transitions transitions, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, ShellTaskOrganizer shellTaskOrganizer) { - if (!DesktopModeStatus.isEnabled() || !Flags.enableDesktopWindowingTaskLimit()) { + if (!DesktopModeStatus.canEnterDesktopMode(context) + || !Flags.enableDesktopWindowingTaskLimit()) { return Optional.empty(); } return Optional.of( @@ -592,23 +599,26 @@ public abstract class WMShellModule { @WMSingleton @Provides static Optional<DesktopTasksTransitionObserver> provideDesktopTasksTransitionObserver( + Context context, Optional<DesktopModeTaskRepository> desktopModeTaskRepository, Transitions transitions, ShellInit shellInit ) { return desktopModeTaskRepository.flatMap(repository -> - Optional.of(new DesktopTasksTransitionObserver(repository, transitions, shellInit)) + Optional.of(new DesktopTasksTransitionObserver( + context, repository, transitions, shellInit)) ); } @WMSingleton @Provides static DesktopModeLoggerTransitionObserver provideDesktopModeLoggerTransitionObserver( + Context context, ShellInit shellInit, Transitions transitions, DesktopModeEventLogger desktopModeEventLogger) { return new DesktopModeLoggerTransitionObserver( - shellInit, transitions, desktopModeEventLogger); + context, shellInit, transitions, desktopModeEventLogger); } @WMSingleton @@ -617,6 +627,12 @@ public abstract class WMShellModule { return new DesktopModeEventLogger(); } + @WMSingleton + @Provides + static ResizeHandleSizeRepository provideResizeHandleSizeRepository() { + return new ResizeHandleSizeRepository(); + } + // // Drag and drop // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java index 795bc1a7113b..d2895b149b2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java @@ -16,9 +16,9 @@ package com.android.wm.shell.dagger.back; -import com.android.wm.shell.back.CrossActivityBackAnimation; import com.android.wm.shell.back.CrossTaskBackAnimation; -import com.android.wm.shell.back.CustomizeActivityAnimation; +import com.android.wm.shell.back.CustomCrossActivityBackAnimation; +import com.android.wm.shell.back.DefaultCrossActivityBackAnimation; import com.android.wm.shell.back.ShellBackAnimation; import com.android.wm.shell.back.ShellBackAnimationRegistry; @@ -47,7 +47,7 @@ public interface ShellBackAnimationModule { @Binds @ShellBackAnimation.CrossActivity ShellBackAnimation bindCrossActivityShellBackAnimation( - CrossActivityBackAnimation crossActivityBackAnimation); + DefaultCrossActivityBackAnimation defaultCrossActivityBackAnimation); /** Default cross task back animation */ @Binds @@ -59,5 +59,5 @@ public interface ShellBackAnimationModule { @Binds @ShellBackAnimation.CustomizeActivity ShellBackAnimation provideCustomizeActivityShellBackAnimation( - CustomizeActivityAnimation customizeActivityAnimation); + CustomCrossActivityBackAnimation customCrossActivityBackAnimation); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index d644006cde81..677fd5deffd3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -60,6 +60,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import com.android.wm.shell.transition.HomeTransitionObserver; import com.android.wm.shell.transition.Transitions; import dagger.Module; @@ -192,11 +193,12 @@ public abstract class Pip1Module { PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState, PipTransitionState pipTransitionState, PhonePipMenuController pipMenuController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + HomeTransitionObserver homeTransitionObserver, Optional<SplitScreenController> splitScreenOptional) { return new PipTransition(context, shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipDisplayLayoutState, pipTransitionState, pipMenuController, pipBoundsAlgorithm, pipAnimationController, pipSurfaceTransactionHelper, - splitScreenOptional); + homeTransitionObserver, splitScreenOptional); } @WMSingleton 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..696831747865 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 @@ -46,6 +46,7 @@ import com.android.wm.shell.pip2.phone.PipTouchHandler; import com.android.wm.shell.pip2.phone.PipTransition; import com.android.wm.shell.pip2.phone.PipTransitionState; import com.android.wm.shell.shared.annotations.ShellMainThread; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -82,6 +83,7 @@ public abstract class Pip2Module { @Provides static Optional<PipController> providePipController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, @@ -97,9 +99,10 @@ public abstract class Pip2Module { return Optional.empty(); } else { return Optional.ofNullable(PipController.create( - context, shellInit, shellController, displayController, displayInsetsController, - pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, pipScheduler, - taskStackListener, shellTaskOrganizer, pipTransitionState, mainExecutor)); + context, shellInit, shellCommandHandler, shellController, displayController, + displayInsetsController, pipBoundsState, pipBoundsAlgorithm, + pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer, + pipTransitionState, mainExecutor)); } } @@ -129,18 +132,22 @@ public abstract class Pip2Module { @Provides static PipTouchHandler providePipTouchHandler(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, PhonePipMenuController menuPhoneController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, + @NonNull PipTransitionState pipTransitionState, + @NonNull PipScheduler pipScheduler, @NonNull SizeSpecSource sizeSpecSource, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, PipUiEventLogger pipUiEventLogger, @ShellMainThread ShellExecutor mainExecutor, Optional<PipPerfHintController> pipPerfHintControllerOptional) { - return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm, - pipBoundsState, sizeSpecSource, pipMotionHelper, floatingContentCoordinator, - pipUiEventLogger, mainExecutor, pipPerfHintControllerOptional); + return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController, + pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler, + sizeSpecSource, pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, + mainExecutor, pipPerfHintControllerOptional); } @WMSingleton @@ -149,14 +156,18 @@ 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 @Provides - static PipTransitionState providePipStackListenerController() { - return new PipTransitionState(); + static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler) { + return new PipTransitionState(handler); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index a10c7c093c60..0b7a3e838e88 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -20,6 +20,7 @@ import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.TaskInfo import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.content.Context import android.os.IBinder import android.util.SparseArray import android.view.SurfaceControl @@ -38,6 +39,7 @@ import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterRe import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -49,6 +51,7 @@ import com.android.wm.shell.util.KtProtoLog * and other transitions that originate both within and outside shell. */ class DesktopModeLoggerTransitionObserver( + context: Context, shellInit: ShellInit, private val transitions: Transitions, private val desktopModeEventLogger: DesktopModeEventLogger @@ -57,7 +60,8 @@ class DesktopModeLoggerTransitionObserver( private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) } init { - if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) { + if (Transitions.ENABLE_SHELL_TRANSITIONS && + DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(this::onInit, this) } } 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..7e0234ef8546 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}") } } @@ -477,11 +446,6 @@ class DesktopModeTaskRepository { * Called when the desktop changes the number of visible freeform tasks. */ fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {} - - /** - * Called when the desktop stashed status changes. - */ - fun onStashedChanged(displayId: Int, stashed: Boolean) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt new file mode 100644 index 000000000000..aa11a7d8a663 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + +import android.util.Log +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger +import com.android.wm.shell.dagger.WMSingleton +import javax.inject.Inject + +/** + * Log Aster UIEvents for desktop windowing mode. + */ +@WMSingleton +class DesktopModeUiEventLogger @Inject constructor( + private val mUiEventLogger: UiEventLogger, + private val mInstanceIdSequence: InstanceIdSequence +) { + /** + * Logs an event for a CUI, on a particular package. + * + * @param uid The user id associated with the package the user is interacting with + * @param packageName The name of the package the user is interacting with + * @param event The event type to generate + */ + fun log(uid: Int, packageName: String, event: DesktopUiEventEnum) { + if (packageName.isEmpty() || uid < 0) { + Log.d(TAG, "Skip logging since package name is empty or bad uid") + return + } + mUiEventLogger.log(event, uid, packageName) + } + + /** + * Retrieves a new instance id for a new interaction. + */ + fun getNewInstanceId(): InstanceId = mInstanceIdSequence.newInstanceId() + + /** + * Logs an event as part of a particular CUI, on a particular package. + * + * @param instanceId The id identifying an interaction, potentially taking place across multiple + * surfaces. There should be a new id generated for each distinct CUI. + * @param uid The user id associated with the package the user is interacting with + * @param packageName The name of the package the user is interacting with + * @param event The event type to generate + */ + fun logWithInstanceId( + instanceId: InstanceId, + uid: Int, + packageName: String, + event: DesktopUiEventEnum + ) { + if (packageName.isEmpty() || uid < 0) { + Log.d(TAG, "Skip logging since package name is empty or bad uid") + return + } + mUiEventLogger.logWithInstanceId(event, uid, packageName, instanceId) + } + + companion object { + /** + * Enums for logging desktop windowing mode UiEvents. + */ + enum class DesktopUiEventEnum(private val mId: Int) : UiEventLogger.UiEventEnum { + + @UiEvent(doc = "Resize the window in desktop windowing mode by dragging the edge") + DESKTOP_WINDOW_EDGE_DRAG_RESIZE(1721), + + @UiEvent(doc = "Resize the window in desktop windowing mode by dragging the corner") + DESKTOP_WINDOW_CORNER_DRAG_RESIZE(1722), + + @UiEvent(doc = "Tap on the window header maximize button in desktop windowing mode") + DESKTOP_WINDOW_MAXIMIZE_BUTTON_TAP(1723), + + @UiEvent(doc = "Double tap on window header to maximize it in desktop windowing mode") + DESKTOP_WINDOW_HEADER_DOUBLE_TAP_TO_MAXIMIZE(1724); + + override fun getId(): Int = mId + } + + private const val TAG = "DesktopModeUiEventLogger" + } +}
\ No newline at end of file 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 ecfb134e45a0..e5bf53a4afdb 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 @@ -71,6 +72,9 @@ import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.shared.DesktopModeStatus +import com.android.wm.shell.shared.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE +import com.android.wm.shell.shared.DesktopModeStatus.isDesktopDensityOverrideSet import com.android.wm.shell.shared.annotations.ExternalThread import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.splitscreen.SplitScreenController @@ -85,7 +89,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 @@ -167,7 +170,7 @@ class DesktopTasksController( init { desktopMode = DesktopModeImpl() - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback({ onInit() }, this) } } @@ -203,6 +206,11 @@ class DesktopTasksController( dragAndDropController.addListener(this) } + @VisibleForTesting + fun getVisualIndicator(): DesktopModeVisualIndicator? { + return visualIndicator + } + fun setOnTaskResizeAnimationListener(listener: OnTaskResizeAnimationListener) { toggleResizeDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) enterDesktopTaskTransitionHandler.setOnTaskResizeAnimationListener(listener) @@ -222,7 +230,7 @@ class DesktopTasksController( bringDesktopAppsToFront(displayId, wct) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - // TODO(b/255649902): ensure remote transition is supplied once state is introduced + // TODO(b/309014605): ensure remote transition is supplied once state is introduced val transitionType = if (remoteTransition == null) TRANSIT_NONE else TRANSIT_TO_FRONT val handler = remoteTransition?.let { OneShotRemoteHandler(transitions.mainExecutor, remoteTransition) @@ -235,34 +243,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 +585,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 +604,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 +846,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 +884,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() && @@ -929,18 +908,22 @@ class DesktopTasksController( task.taskId ) return WindowContainerTransaction().also { wct -> - addMoveToFullscreenChanges(wct, task) + bringDesktopAppsToFrontBeforeShowingNewTask(task.displayId, wct, task.taskId) + wct.reorder(task.token, true) } } + val wct = WindowContainerTransaction() + if (isDesktopDensityOverrideSet()) { + wct.setDensityDpi(task.token, DESKTOP_DENSITY_OVERRIDE) + } // Desktop Mode is showing and we're launching a new Task - we might need to minimize // a Task. - val wct = WindowContainerTransaction() val taskToMinimize = addAndGetMinimizeChangesIfNeeded(task.displayId, wct, task) if (taskToMinimize != null) { addPendingMinimizeTransition(transition, taskToMinimize) return wct } - return null + return if (wct.isEmpty) null else wct } private fun handleFullscreenTaskLaunch( @@ -966,24 +949,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 +976,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,10 +985,13 @@ 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()) { - wct.setDensityDpi(taskInfo.token, getDesktopDensityDpi()) + wct.setDensityDpi(taskInfo.token, DESKTOP_DENSITY_OVERRIDE) } } @@ -1126,10 +1095,6 @@ class DesktopTasksController( return context.resources.displayMetrics.densityDpi } - private fun getDesktopDensityDpi(): Int { - return DESKTOP_DENSITY_OVERRIDE - } - /** Creates a new instance of the external interface to pass to another process. */ private fun createExternalInterface(): ExternalInterfaceBinder { return IDesktopModeImpl(this) @@ -1239,13 +1204,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 -> { @@ -1408,16 +1377,6 @@ class DesktopTasksController( l -> l.onTasksVisibilityChanged(displayId, visibleTasksCount) } } - - override fun onStashedChanged(displayId: Int, stashed: Boolean) { - KtProtoLog.v( - WM_SHELL_DESKTOP_MODE, - "IDesktopModeImpl: onStashedChanged display=%d stashed=%b", - displayId, - stashed - ) - remoteListener.call { l -> l.onStashedChanged(displayId, stashed) } - } } init { @@ -1449,25 +1408,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 { @@ -1509,21 +1468,9 @@ class DesktopTasksController( } companion object { - private val DESKTOP_DENSITY_OVERRIDE = - SystemProperties.getInt("persist.wm.debug.desktop_mode_density", 284) - private val DESKTOP_DENSITY_ALLOWED_RANGE = (100..1000) - @JvmField val DESKTOP_MODE_INITIAL_BOUNDS_SCALE = SystemProperties .getInt("persist.wm.debug.desktop_mode_initial_bounds_scale", 75) / 100f - - /** - * Check if desktop density override is enabled - */ - @JvmStatic - fun isDesktopDensityOverrideSet(): Boolean { - return DESKTOP_DENSITY_OVERRIDE in DESKTOP_DENSITY_ALLOWED_RANGE - } } /** The positions on a screen that a task can snap to. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 3404d376fe92..0f88384ec2ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -25,6 +25,7 @@ import android.window.WindowContainerTransaction import androidx.annotation.VisibleForTesting import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionObserver import com.android.wm.shell.util.KtProtoLog diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt index 20df26428649..dae75f90e3ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt @@ -16,12 +16,14 @@ package com.android.wm.shell.desktopmode +import android.content.Context import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager import android.window.TransitionInfo import com.android.window.flags.Flags.enableDesktopWindowingWallpaperActivity import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.KtProtoLog @@ -33,13 +35,15 @@ import com.android.wm.shell.util.KtProtoLog * and other transitions that originate both within and outside shell. */ class DesktopTasksTransitionObserver( + context: Context, private val desktopModeTaskRepository: DesktopModeTaskRepository, private val transitions: Transitions, shellInit: ShellInit ) : Transitions.TransitionObserver { init { - if (Transitions.ENABLE_SHELL_TRANSITIONS && DesktopModeStatus.isEnabled()) { + if (Transitions.ENABLE_SHELL_TRANSITIONS && + DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(::onInit, this) } } 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/desktopmode/IDesktopTaskListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl index 8ed87f23bf40..8ebdfdcf4731 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopTaskListener.aidl @@ -25,6 +25,6 @@ interface IDesktopTaskListener { /** Desktop tasks visibility has changed. Visible if at least 1 task is visible. */ oneway void onTasksVisibilityChanged(int displayId, int visibleTasksCount); - /** Desktop task stashed status has changed. */ + /** @deprecated this is no longer supported. */ oneway void onStashedChanged(int displayId, boolean stashed); }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index 6a7d297e83e5..a42ca1905ee7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -16,10 +16,9 @@ package com.android.wm.shell.draganddrop; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED; -import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.ClipDescription.EXTRA_ACTIVITY_OPTIONS; @@ -47,7 +46,6 @@ import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.content.ActivityNotFoundException; -import android.content.ClipData; import android.content.ClipDescription; import android.content.Context; import android.content.Intent; @@ -265,13 +263,14 @@ public class DragAndDropPolicy { final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT); final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic(); baseActivityOpts.setDisallowEnterPictureInPictureWhileLaunching(true); + // Put BAL flags to avoid activity start aborted. + baseActivityOpts.setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + baseActivityOpts.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); final Bundle opts = baseActivityOpts.toBundle(); if (session.appData.hasExtra(EXTRA_ACTIVITY_OPTIONS)) { opts.putAll(session.appData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)); } - // Put BAL flags to avoid activity start aborted. - opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); - opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true); final UserHandle user = session.appData.getParcelableExtra(EXTRA_USER); if (isTask) { 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..4bb10dfdf8c6 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,17 +16,17 @@ 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; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; +import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT; @@ -41,7 +41,6 @@ import android.app.StatusBarManager; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Color; import android.graphics.Insets; import android.graphics.Rect; import android.graphics.Region; @@ -278,7 +277,7 @@ public class DragLayout extends LinearLayout final int activityType = taskInfo1.getActivityType(); if (activityType == ACTIVITY_TYPE_STANDARD) { Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo); - int bgColor1 = getResizingBackgroundColor(taskInfo1); + int bgColor1 = getResizingBackgroundColor(taskInfo1).toArgb(); mDropZoneView1.setAppInfo(bgColor1, icon1); mDropZoneView2.setAppInfo(bgColor1, icon1); updateDropZoneSizes(null, null); // passing null splits the views evenly @@ -298,10 +297,10 @@ public class DragLayout extends LinearLayout mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT); if (topOrLeftTask != null && bottomOrRightTask != null) { Drawable topOrLeftIcon = mIconProvider.getIcon(topOrLeftTask.topActivityInfo); - int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask); + int topOrLeftColor = getResizingBackgroundColor(topOrLeftTask).toArgb(); Drawable bottomOrRightIcon = mIconProvider.getIcon( bottomOrRightTask.topActivityInfo); - int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask); + int bottomOrRightColor = getResizingBackgroundColor(bottomOrRightTask).toArgb(); mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon); mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon); } @@ -516,20 +515,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(); @@ -558,11 +555,6 @@ public class DragLayout extends LinearLayout } } - private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) { - final int taskBgColor = taskInfo.taskDescription.getBackgroundColor(); - return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); - } - /** * Dumps information about this drag layout. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java index 6fea2036dbd1..e0e2e706d649 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java @@ -21,14 +21,15 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM; import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; import android.util.SparseArray; import android.view.SurfaceControl; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -44,6 +45,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener { private static final String TAG = "FreeformTaskListener"; + private final Context mContext; private final ShellTaskOrganizer mShellTaskOrganizer; private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository; private final WindowDecorViewModel mWindowDecorationViewModel; @@ -56,10 +58,12 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, } public FreeformTaskListener( + Context context, ShellInit shellInit, ShellTaskOrganizer shellTaskOrganizer, Optional<DesktopModeTaskRepository> desktopModeTaskRepository, WindowDecorViewModel windowDecorationViewModel) { + mContext = context; mShellTaskOrganizer = shellTaskOrganizer; mWindowDecorationViewModel = windowDecorationViewModel; mDesktopModeTaskRepository = desktopModeTaskRepository; @@ -70,7 +74,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, private void onInit() { mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM); - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(mContext)) { mShellTaskOrganizer.addFocusListener(this); } } @@ -92,7 +96,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, t.apply(); } - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(mContext)) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); @@ -114,7 +118,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, taskInfo.taskId); mTasks.remove(taskInfo.taskId); - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(mContext)) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.removeFreeformTask(taskInfo.taskId); repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); @@ -125,7 +129,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, repository.updateVisibleFreeformTasks(taskInfo.displayId, taskInfo.taskId, false); }); } - + mWindowDecorationViewModel.onTaskVanished(taskInfo); if (!Transitions.ENABLE_SHELL_TRANSITIONS) { mWindowDecorationViewModel.destroyWindowDecoration(taskInfo); } @@ -139,7 +143,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, taskInfo.taskId); mWindowDecorationViewModel.onTaskInfoChanged(taskInfo); state.mTaskInfo = taskInfo; - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(mContext)) { mDesktopModeTaskRepository.ifPresent(repository -> { if (taskInfo.isVisible) { if (repository.addActiveTask(taskInfo.displayId, taskInfo.taskId)) { @@ -161,7 +165,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Focus Changed: #%d focused=%b", taskInfo.taskId, taskInfo.isFocused); - if (DesktopModeStatus.isEnabled() && taskInfo.isFocused) { + if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.isFocused) { mDesktopModeTaskRepository.ifPresent(repository -> { repository.addOrMoveFreeformTaskToTop(taskInfo.taskId); repository.unminimizeTask(taskInfo.displayId, taskInfo.taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index 998728d65e6a..2626e7380163 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -161,7 +161,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d", taskInfo.taskId); mTasks.remove(taskInfo.taskId); - + mWindowDecorViewModelOptional.ifPresent(v -> v.onTaskVanished(taskInfo)); if (Transitions.ENABLE_SHELL_TRANSITIONS) return; if (mWindowDecorViewModelOptional.isPresent()) { mWindowDecorViewModelOptional.get().destroyWindowDecoration(taskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java index 9eaf7e4e2e21..c79eef7efb61 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.service.dreams.Flags.dismissDreamOnKeyguardDismiss; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; +import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING; @@ -83,6 +84,7 @@ public class KeyguardTransitionHandler * @see KeyguardTransitions */ private IRemoteTransition mExitTransition = null; + private IRemoteTransition mAppearTransition = null; private IRemoteTransition mOccludeTransition = null; private IRemoteTransition mOccludeByDreamTransition = null; private IRemoteTransition mUnoccludeTransition = null; @@ -170,26 +172,28 @@ public class KeyguardTransitionHandler // Choose a transition applicable for the changes and keyguard state. if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) { - return startAnimation(mExitTransition, - "going-away", + return startAnimation(mExitTransition, "going-away", transition, info, startTransaction, finishTransaction, finishCallback); } + if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) { + return startAnimation(mAppearTransition, "appearing", + transition, info, startTransaction, finishTransaction, finishCallback); + } + + // Occlude/unocclude animations are only played if the keyguard is locked. if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) { if (hasOpeningDream(info)) { - return startAnimation(mOccludeByDreamTransition, - "occlude-by-dream", + return startAnimation(mOccludeByDreamTransition, "occlude-by-dream", transition, info, startTransaction, finishTransaction, finishCallback); } else { - return startAnimation(mOccludeTransition, - "occlude", + return startAnimation(mOccludeTransition, "occlude", transition, info, startTransaction, finishTransaction, finishCallback); } } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) { - return startAnimation(mUnoccludeTransition, - "unocclude", + return startAnimation(mUnoccludeTransition, "unocclude", transition, info, startTransaction, finishTransaction, finishCallback); } } @@ -359,11 +363,13 @@ public class KeyguardTransitionHandler @Override public void register( IRemoteTransition exitTransition, + IRemoteTransition appearTransition, IRemoteTransition occludeTransition, IRemoteTransition occludeByDreamTransition, IRemoteTransition unoccludeTransition) { mMainExecutor.execute(() -> { mExitTransition = exitTransition; + mAppearTransition = appearTransition; mOccludeTransition = occludeTransition; mOccludeByDreamTransition = occludeByDreamTransition; mUnoccludeTransition = unoccludeTransition; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java index 4215b2cc5f29..b7245b91f36c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitions.java @@ -35,6 +35,7 @@ public interface KeyguardTransitions { */ default void register( @NonNull IRemoteTransition unlockTransition, + @NonNull IRemoteTransition appearTransition, @NonNull IRemoteTransition occludeTransition, @NonNull IRemoteTransition occludeByDreamTransition, @NonNull IRemoteTransition unoccludeTransition) {} 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/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index e885262658f4..e1657f99639d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -854,7 +854,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipUiEventLoggerLogger.log(uiEventEnum); ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "onTaskAppeared: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState); + "onTaskAppeared: %s, state=%s, taskId=%s", mTaskInfo.topActivity, + mPipTransitionState, mTaskInfo.taskId); if (mPipTransitionState.getInSwipePipToHomeTransition()) { if (!mWaitForFixedRotation) { onEndOfSwipePipToHomeTransition(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index fdde3ee01264..3c7713d30714 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -75,6 +75,7 @@ import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.CounterRotatorHelper; +import com.android.wm.shell.transition.HomeTransitionObserver; import com.android.wm.shell.transition.Transitions; import java.io.PrintWriter; @@ -107,6 +108,7 @@ public class PipTransition extends PipTransitionController { private final PipDisplayLayoutState mPipDisplayLayoutState; private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; + private final HomeTransitionObserver mHomeTransitionObserver; private final Optional<SplitScreenController> mSplitScreenOptional; private final PipAnimationController mPipAnimationController; private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; @@ -164,6 +166,7 @@ public class PipTransition extends PipTransitionController { PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, + HomeTransitionObserver homeTransitionObserver, Optional<SplitScreenController> splitScreenOptional) { super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, pipBoundsAlgorithm); @@ -174,6 +177,7 @@ public class PipTransition extends PipTransitionController { mEnterExitAnimationDuration = context.getResources() .getInteger(R.integer.config_pipResizeAnimationDuration); mSurfaceTransactionHelper = pipSurfaceTransactionHelper; + mHomeTransitionObserver = homeTransitionObserver; mSplitScreenOptional = splitScreenOptional; } @@ -196,6 +200,9 @@ public class PipTransition extends PipTransitionController { animator.cancel(); } mExitTransition = mTransitions.startTransition(type, out, this); + if (mPipOrganizer.getOutPipWindowingMode() == WINDOWING_MODE_UNDEFINED) { + mHomeTransitionObserver.notifyHomeVisibilityChanged(false /* isVisible */); + } } @Override @@ -824,12 +831,10 @@ public class PipTransition extends PipTransitionController { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo) { startTransaction.apply(); - if (info.getChanges().isEmpty()) { + final TransitionInfo.Change pipChange = findCurrentPipTaskChange(info); + if (pipChange == null) { ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "removePipImmediately is called with empty changes"); - } else { - finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), - mPipDisplayLayoutState.getDisplayBounds()); + "removePipImmediately is called without pip change"); } mPipOrganizer.onExitPipFinished(taskInfo); finishCallback.onTransitionFinished(null); 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/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index a12882f56eb7..f5afeea3eaef 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -58,9 +58,12 @@ import com.android.wm.shell.common.pip.PipDisplayLayoutState; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; +import java.io.PrintWriter; + /** * Manages the picture-in-picture (PIP) UI and states for Phones. */ @@ -72,6 +75,7 @@ public class PipController implements ConfigurationChangeListener, private static final String SWIPE_TO_PIP_OVERLAY = "swipe_to_pip_overlay"; private final Context mContext; + private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; private final DisplayController mDisplayController; private final DisplayInsetsController mDisplayInsetsController; @@ -111,6 +115,7 @@ public class PipController implements ConfigurationChangeListener, private PipController(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, @@ -123,6 +128,7 @@ public class PipController implements ConfigurationChangeListener, PipTransitionState pipTransitionState, ShellExecutor mainExecutor) { mContext = context; + mShellCommandHandler = shellCommandHandler; mShellController = shellController; mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; @@ -146,6 +152,7 @@ public class PipController implements ConfigurationChangeListener, */ public static PipController create(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, ShellController shellController, DisplayController displayController, DisplayInsetsController displayInsetsController, @@ -162,13 +169,14 @@ public class PipController implements ConfigurationChangeListener, "%s: Device doesn't support Pip feature", TAG); return null; } - return new PipController(context, shellInit, shellController, displayController, - displayInsetsController, pipBoundsState, pipBoundsAlgorithm, pipDisplayLayoutState, - pipScheduler, taskStackListener, shellTaskOrganizer, pipTransitionState, - mainExecutor); + return new PipController(context, shellInit, shellCommandHandler, shellController, + displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm, + pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer, + pipTransitionState, mainExecutor); } private void onInit() { + mShellCommandHandler.addDumpCallback(this::dump, this); // Ensure that we have the display info in case we get calls to update the bounds before the // listener calls back mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId()); @@ -338,6 +346,14 @@ public class PipController implements ConfigurationChangeListener, } } + private void dump(PrintWriter pw, String prefix) { + final String innerPrefix = " "; + pw.println(TAG); + mPipBoundsAlgorithm.dump(pw, innerPrefix); + mPipBoundsState.dump(pw, innerPrefix); + mPipDisplayLayoutState.dump(pw, innerPrefix); + } + /** * The interface for calls from outside the host process. */ 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..aed493f2bc8f 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; @@ -55,8 +58,10 @@ import java.util.function.Consumer; * A helper to animate and manipulate the PiP. */ public class PipMotionHelper implements PipAppOpsListener.Callback, - FloatingContentCoordinator.FloatingContent { + FloatingContentCoordinator.FloatingContent, + PipTransitionState.PipTransitionStateChangedListener { 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 +77,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 +152,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 +164,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); } void init() { @@ -236,12 +251,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 +562,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 +633,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 +665,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 +688,74 @@ public class PipMotionHelper implements PipAppOpsListener.Callback, // setAnimatingToBounds(toBounds); } + @Override + public 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 (mPipBoundsState.getBounds().equals( + mPipBoundsState.getMotionBoundsState().getBoundsInMotion())) { + // Avoid scheduling transitions for bounds that don't change, such transition is + // a no-op and would be aborted. + settlePipBoundsAfterPhysicsAnimation(false /* animatingAfter */); + cleanUpHighPerfSessionMaybe(); + // SCHEDULED_BOUNDS_CHANGE can have multiple active listeners making + // actual changes (e.g. PipTouchHandler). So post state update onto handler, + // to run after synchronous dispatch is complete. + mPipTransitionState.postState(PipTransitionState.CHANGED_PIP_BOUNDS); + 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/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java index 04cf350ddd3e..7dffe543ec9c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -24,6 +24,7 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.hardware.input.InputManager; +import android.os.Bundle; import android.os.Looper; import android.view.BatchedInputEventReceiver; import android.view.Choreographer; @@ -32,6 +33,7 @@ import android.view.InputEvent; import android.view.InputEventReceiver; import android.view.InputMonitor; import android.view.MotionEvent; +import android.view.SurfaceControl; import android.view.ViewConfiguration; import androidx.annotation.VisibleForTesting; @@ -51,16 +53,20 @@ import java.util.function.Consumer; * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to * trigger dynamic resize. */ -public class PipResizeGestureHandler { +public class PipResizeGestureHandler implements + PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = "PipResizeGestureHandler"; private static final int PINCH_RESIZE_SNAP_DURATION = 250; private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f; + private static final String RESIZE_BOUNDS_CHANGE = "resize_bounds_change"; private final Context mContext; private final PipBoundsAlgorithm mPipBoundsAlgorithm; private final PipBoundsState mPipBoundsState; private final PipTouchState mPipTouchState; + private final PipScheduler mPipScheduler; + private final PipTransitionState mPipTransitionState; private final PhonePipMenuController mPhonePipMenuController; private final PipUiEventLogger mPipUiEventLogger; private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; @@ -88,6 +94,7 @@ public class PipResizeGestureHandler { private boolean mIsSysUiStateValid; private boolean mThresholdCrossed; private boolean mOngoingPinchToResize = false; + private boolean mWaitingForBoundsChangeTransition = false; private float mAngle = 0; int mFirstIndex = -1; int mSecondIndex = -1; @@ -104,11 +111,17 @@ public class PipResizeGestureHandler { private int mCtrlType; private int mOhmOffset; - public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm, - PipBoundsState pipBoundsState, PipTouchState pipTouchState, + public PipResizeGestureHandler(Context context, + PipBoundsAlgorithm pipBoundsAlgorithm, + PipBoundsState pipBoundsState, + PipTouchState pipTouchState, + PipScheduler pipScheduler, + PipTransitionState pipTransitionState, Runnable updateMovementBoundsRunnable, - PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, - ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) { + PipUiEventLogger pipUiEventLogger, + PhonePipMenuController menuActivityController, + ShellExecutor mainExecutor, + @Nullable PipPerfHintController pipPerfHintController) { mContext = context; mDisplayId = context.getDisplayId(); mMainExecutor = mainExecutor; @@ -116,6 +129,11 @@ public class PipResizeGestureHandler { mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; mPipTouchState = pipTouchState; + mPipScheduler = pipScheduler; + + mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this); + mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; mPhonePipMenuController = menuActivityController; mPipUiEventLogger = pipUiEventLogger; @@ -125,6 +143,7 @@ public class PipResizeGestureHandler { mUserResizeBounds.set(rect); // mMotionHelper.synchronizePinnedStackBounds(); mUpdateMovementBoundsRunnable.run(); + mPipBoundsState.setBounds(rect); resetState(); }; } @@ -202,7 +221,7 @@ public class PipResizeGestureHandler { @VisibleForTesting void onInputEvent(InputEvent ev) { if (!mEnablePinchResize) { - // No need to handle anything if neither form of resizing is enabled. + // No need to handle anything if resizing isn't enabled. return; } @@ -227,7 +246,7 @@ public class PipResizeGestureHandler { } } - if (mEnablePinchResize && mOngoingPinchToResize) { + if (mOngoingPinchToResize) { onPinchResize(mv); } } @@ -249,13 +268,11 @@ public class PipResizeGestureHandler { } boolean willStartResizeGesture(MotionEvent ev) { - if (isInValidSysUiState()) { - if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { - if (mEnablePinchResize && ev.getPointerCount() == 2) { - onPinchResize(ev); - mOngoingPinchToResize = mAllowGesture; - return mAllowGesture; - } + if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { + if (mEnablePinchResize && ev.getPointerCount() == 2) { + onPinchResize(ev); + mOngoingPinchToResize = mAllowGesture; + return mAllowGesture; } } return false; @@ -284,7 +301,6 @@ public class PipResizeGestureHandler { mSecondIndex = -1; mAllowGesture = false; finishResize(); - cleanUpHighPerfSessionMaybe(); } if (ev.getPointerCount() != 2) { @@ -347,10 +363,7 @@ public class PipResizeGestureHandler { mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize, mDownBounds, mLastResizeBounds); - /* - mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, - mAngle, null); - */ + mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle); mPipBoundsState.setHasUserResizedPip(true); } } @@ -399,57 +412,43 @@ public class PipResizeGestureHandler { } private void finishResize() { - if (!mLastResizeBounds.isEmpty()) { - // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped - // position correctly. Drag-resize does not need to move, so just finalize resize. - if (mOngoingPinchToResize) { - final Rect startBounds = new Rect(mLastResizeBounds); - // If user resize is pretty close to max size, just auto resize to max. - if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x - || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { - resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); - } + if (mLastResizeBounds.isEmpty()) { + resetState(); + } + if (!mOngoingPinchToResize) { + return; + } + final Rect startBounds = new Rect(mLastResizeBounds); - // If user resize is smaller than min size, auto resize to min - if (mLastResizeBounds.width() < mMinSize.x - || mLastResizeBounds.height() < mMinSize.y) { - resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y); - } + // If user resize is pretty close to max size, just auto resize to max. + if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x + || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); + } - // get the current movement bounds - final Rect movementBounds = mPipBoundsAlgorithm - .getMovementBounds(mLastResizeBounds); - - // snap mLastResizeBounds to the correct edge based on movement bounds - snapToMovementBoundsEdge(mLastResizeBounds, movementBounds); - - final float snapFraction = mPipBoundsAlgorithm.getSnapFraction( - mLastResizeBounds, movementBounds); - mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); - - // disable any touch events beyond resizing too - mPipTouchState.setAllowInputEvents(false); - - /* - mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, - PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> { - // enable touch events - mPipTouchState.setAllowInputEvents(true); - }); - */ - } else { - /* - mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, - TRANSITION_DIRECTION_USER_RESIZE, - mUpdateResizeBoundsCallback); - */ - } - final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f; - mPipUiEventLogger.log( - PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); - } else { - resetState(); + // If user resize is smaller than min size, auto resize to min + if (mLastResizeBounds.width() < mMinSize.x + || mLastResizeBounds.height() < mMinSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y); } + + // get the current movement bounds + final Rect movementBounds = mPipBoundsAlgorithm + .getMovementBounds(mLastResizeBounds); + + // snap mLastResizeBounds to the correct edge based on movement bounds + snapToMovementBoundsEdge(mLastResizeBounds, movementBounds); + + final float snapFraction = mPipBoundsAlgorithm.getSnapFraction( + mLastResizeBounds, movementBounds); + mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); + + // Update the transition state to schedule a resize transition. + Bundle extra = new Bundle(); + extra.putBoolean(RESIZE_BOUNDS_CHANGE, true); + mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra); + + mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); } private void resetState() { @@ -509,6 +508,51 @@ public class PipResizeGestureHandler { rect.set(l, t, r, b); } + @Override + public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState, + @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) { + switch (newState) { + case PipTransitionState.SCHEDULED_BOUNDS_CHANGE: + if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break; + + if (mPipBoundsState.getBounds().equals(mLastResizeBounds)) { + // If the bounds are invariant move the destination bounds by a single pixel + // to top/bottom to avoid a no-op transition. This trick helps keep the + // animation a part of the transition. + float snapFraction = mPipBoundsAlgorithm.getSnapFraction( + mPipBoundsState.getBounds()); + + // Move to the top if closer to the bottom edge and vice versa. + boolean inTopHalf = snapFraction < 1.5 || snapFraction > 3.5; + int offsetY = inTopHalf ? 1 : -1; + mLastResizeBounds.offset(0 /* dx */, offsetY); + } + + mWaitingForBoundsChangeTransition = true; + mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds); + 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.apply(); + + // All motion operations have actually finished, so make bounds cache updates. + mUpdateResizeBoundsCallback.accept(destinationBounds); + cleanUpHighPerfSessionMaybe(); + + // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core. + mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS); + break; + } + } + /** * Dumps the {@link PipResizeGestureHandler} state. */ 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..49475077211f 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 @@ -24,17 +24,21 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.graphics.Matrix; 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 +160,39 @@ 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) { + scheduleUserResizePip(toBounds, 0f /* degrees */); + } + + /** + * Directly perform a scaled matrix transformation on the leash. This will not perform any + * {@link WindowContainerTransaction}. + * + * @param degrees the angle to rotate the bounds to. + */ + public void scheduleUserResizePip(Rect toBounds, float degrees) { + 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(); + + Matrix transformTensor = new Matrix(); + final float[] mMatrixTmp = new float[9]; + final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width(); + + transformTensor.setScale(scale, scale); + transformTensor.postTranslate(toBounds.left, toBounds.top); + transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY()); + + tx.setMatrix(leash, transformTensor, mMatrixTmp); + 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..56a465a4889a 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; @@ -61,6 +64,7 @@ import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.pip.SizeSpecSource; import com.android.wm.shell.pip.PipAnimationController; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; import java.io.PrintWriter; @@ -70,7 +74,7 @@ import java.util.Optional; * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding * the PIP. */ -public class PipTouchHandler { +public class PipTouchHandler implements PipTransitionState.PipTransitionStateChangedListener { private static final String TAG = "PipTouchHandler"; private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f; @@ -78,8 +82,11 @@ public class PipTouchHandler { // Allow PIP to resize to a slightly bigger state upon touch private boolean mEnableResize; private final Context mContext; + private final ShellCommandHandler mShellCommandHandler; private final PipBoundsAlgorithm mPipBoundsAlgorithm; @NonNull private final PipBoundsState mPipBoundsState; + @NonNull private final PipTransitionState mPipTransitionState; + @NonNull private final PipScheduler mPipScheduler; @NonNull private final SizeSpecSource mSizeSpecSource; private final PipUiEventLogger mPipUiEventLogger; private final PipDismissTargetHandler mPipDismissTargetHandler; @@ -125,6 +132,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(); @@ -164,9 +172,12 @@ public class PipTouchHandler { @SuppressLint("InflateParams") public PipTouchHandler(Context context, ShellInit shellInit, + ShellCommandHandler shellCommandHandler, PhonePipMenuController menuController, PipBoundsAlgorithm pipBoundsAlgorithm, @NonNull PipBoundsState pipBoundsState, + @NonNull PipTransitionState pipTransitionState, + @NonNull PipScheduler pipScheduler, @NonNull SizeSpecSource sizeSpecSource, PipMotionHelper pipMotionHelper, FloatingContentCoordinator floatingContentCoordinator, @@ -174,11 +185,16 @@ public class PipTouchHandler { ShellExecutor mainExecutor, Optional<PipPerfHintController> pipPerfHintControllerOptional) { mContext = context; + mShellCommandHandler = shellCommandHandler; mMainExecutor = mainExecutor; mPipPerfHintController = pipPerfHintControllerOptional.orElse(null); mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mPipBoundsAlgorithm = pipBoundsAlgorithm; mPipBoundsState = pipBoundsState; + + mPipTransitionState = pipTransitionState; + mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged); + mPipScheduler = pipScheduler; mSizeSpecSource = sizeSpecSource; mMenuController = menuController; mPipUiEventLogger = pipUiEventLogger; @@ -204,10 +220,10 @@ public class PipTouchHandler { }, menuController::hideMenu, mainExecutor); - mPipResizeGestureHandler = - new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState, - mTouchState, this::updateMovementBounds, pipUiEventLogger, - menuController, mainExecutor, mPipPerfHintController); + mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm, + pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState, + this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor, + mPipPerfHintController); mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize); if (PipUtils.isPip2ExperimentEnabled()) { @@ -223,10 +239,16 @@ public class PipTouchHandler { mEnableResize = res.getBoolean(R.bool.config_pipEnableResizeForMenu); reloadResources(); + mShellCommandHandler.addDumpCallback(this::dump, this); mMotionHelper.init(); 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 +316,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 +532,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 +549,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 +831,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 +903,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 +1083,28 @@ public class PipTouchHandler { mPipResizeGestureHandler.setOhmOffset(offset); } + @Override + public 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 12dce5bf70c0..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 @@ -45,7 +45,6 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.util.Preconditions; -import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.pip.PipBoundsAlgorithm; import com.android.wm.shell.common.pip.PipBoundsState; @@ -62,9 +61,16 @@ 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"; + // 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. * The fadeout animation is guaranteed to start after the client has drawn under the new config. @@ -98,6 +104,8 @@ public class PipTransition extends PipTransitionController implements private WindowContainerToken mPipTaskToken; @Nullable private SurfaceControl mPipLeash; + @Nullable + private Transitions.TransitionFinishCallback mFinishCallback; public PipTransition( Context context, @@ -223,7 +231,6 @@ public class PipTransition extends PipTransitionController implements return startExpandAnimation(info, startTransaction, finishTransaction, finishCallback); } else if (transition == mResizeTransition) { mResizeTransition = null; - mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS); return startResizeAnimation(info, startTransaction, finishTransaction, finishCallback); } @@ -246,31 +253,27 @@ public class PipTransition extends PipTransitionController implements return false; } SurfaceControl pipLeash = pipChange.getLeash(); - Rect destinationBounds = pipChange.getEndAbsBounds(); // Even though the final bounds and crop are applied with finishTransaction since // this is a visible change, we still need to handle the app draw coming in. Snapshot // covering app draw during collection will be removed by startTransaction. So we make - // the crop equal to the final bounds and then scale the leash back to starting bounds. + // the crop equal to the final bounds and then let the current + // animator scale the leash back to starting bounds. + // Note: animator is responsible for applying the startTx but NOT finishTx. startTransaction.setWindowCrop(pipLeash, pipChange.getEndAbsBounds().width(), pipChange.getEndAbsBounds().height()); - startTransaction.setScale(pipLeash, - (float) mPipBoundsState.getBounds().width() / destinationBounds.width(), - (float) mPipBoundsState.getBounds().height() / destinationBounds.height()); - startTransaction.apply(); - finishTransaction.setScale(pipLeash, - (float) mPipBoundsState.getBounds().width() / destinationBounds.width(), - (float) mPipBoundsState.getBounds().height() / destinationBounds.height()); - - // We are done with the transition, but will continue animating leash to final bounds. - finishCallback.onTransitionFinished(null); - - // Animate the pip leash with the new buffer - final int duration = mContext.getResources().getInteger( - R.integer.config_pipResizeAnimationDuration); // TODO: b/275910498 Couple this routine with a new implementation of the PiP animator. - startResizeAnimation(pipLeash, mPipBoundsState.getBounds(), destinationBounds, duration); + // Classes interested in continuing the animation would subscribe to this state update + // getting info such as endBounds, startTx, and finishTx as an extra Bundle once + // animators are in place. Once done state needs to be updated to CHANGED_PIP_BOUNDS. + Bundle extra = new Bundle(); + extra.putParcelable(PIP_START_TX, startTransaction); + extra.putParcelable(PIP_FINISH_TX, finishTransaction); + extra.putParcelable(PIP_DESTINATION_BOUNDS, pipChange.getEndAbsBounds()); + + mFinishCallback = finishCallback; + mPipTransitionState.setState(PipTransitionState.CHANGING_PIP_BOUNDS, extra); return true; } @@ -285,12 +288,17 @@ public class PipTransition extends PipTransitionController implements WindowContainerToken pipTaskToken = pipChange.getContainer(); SurfaceControl pipLeash = pipChange.getLeash(); + if (pipTaskToken == null || pipLeash == null) { + return false; + } + PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams; Rect srcRectHint = params.getSourceRectHint(); Rect startBounds = pipChange.getStartAbsBounds(); Rect destinationBounds = pipChange.getEndAbsBounds(); WindowContainerTransaction finishWct = new WindowContainerTransaction(); + SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); if (PipBoundsAlgorithm.isSourceRectHintValidForEnterPip(srcRectHint, destinationBounds)) { final float scale = (float) destinationBounds.width() / srcRectHint.width(); @@ -316,19 +324,17 @@ public class PipTransition extends PipTransitionController implements .reparent(overlayLeash, pipLeash) .setLayer(overlayLeash, Integer.MAX_VALUE); - if (pipTaskToken != null) { - SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); - tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), - this::onClientDrawAtTransitionEnd) - .setScale(overlayLeash, 1f, 1f) - .setPosition(overlayLeash, - (destinationBounds.width() - overlaySize) / 2f, - (destinationBounds.height() - overlaySize) / 2f); - finishWct.setBoundsChangeTransaction(pipTaskToken, tx); - } + // Overlay needs to be adjusted once a new draw comes in resetting surface transform. + tx.setScale(overlayLeash, 1f, 1f); + tx.setPosition(overlayLeash, (destinationBounds.width() - overlaySize) / 2f, + (destinationBounds.height() - overlaySize) / 2f); } startTransaction.apply(); + tx.addTransactionCommittedListener(mPipScheduler.getMainExecutor(), + this::onClientDrawAtTransitionEnd); + finishWct.setBoundsChangeTransaction(pipTaskToken, tx); + // Note that finishWct should be free of any actual WM state changes; we are using // it for syncing with the client draw after delayed configuration changes are dispatched. finishCallback.onTransitionFinished(finishWct.isEmpty() ? null : finishWct); @@ -412,14 +418,6 @@ public class PipTransition extends PipTransitionController implements return true; } - /** - * TODO: b/275910498 Use a new implementation of the PiP animator here. - */ - private void startResizeAnimation(SurfaceControl leash, Rect startBounds, - Rect endBounds, int duration) { - mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS); - } - // // Various helpers to resolve transition requests and infos // @@ -537,6 +535,15 @@ public class PipTransition extends PipTransitionController implements mPipTransitionState.mPipTaskToken = null; mPipTransitionState.mPinnedTaskLeash = null; break; + case PipTransitionState.CHANGED_PIP_BOUNDS: + // Note: this might not be the end of the animation, rather animator just finished + // adjusting startTx and finishTx and is ready to finishTransition(). The animator + // can still continue playing the leash into the destination bounds after. + if (mFinishCallback != null) { + mFinishCallback.onTransitionFinished(null); + mFinishCallback = null; + } + break; } } } 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 f7bc622b6195..9d599caf13dd 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 @@ -19,6 +19,7 @@ package com.android.wm.shell.pip2.phone; import android.annotation.IntDef; import android.graphics.Rect; import android.os.Bundle; +import android.os.Handler; import android.view.SurfaceControl; import android.window.WindowContainerToken; @@ -26,6 +27,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.util.Preconditions; +import com.android.wm.shell.shared.annotations.ShellMainThread; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -71,17 +73,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 +98,7 @@ public class PipTransitionState { SWIPING_TO_PIP, ENTERING_PIP, ENTERED_PIP, + SCHEDULED_BOUNDS_CHANGE, CHANGING_PIP_BOUNDS, CHANGED_PIP_BOUNDS, EXITING_PIP, @@ -104,6 +111,13 @@ public class PipTransitionState { private int mState; // + // Dependencies + // + + @ShellMainThread + private final Handler mMainHandler; + + // // Swipe up to enter PiP related state // @@ -144,6 +158,10 @@ public class PipTransitionState { private final List<PipTransitionStateChangedListener> mCallbacks = new ArrayList<>(); + public PipTransitionState(@ShellMainThread Handler handler) { + mMainHandler = handler; + } + /** * @return the state of PiP in the context of transitions. */ @@ -165,10 +183,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); @@ -176,6 +195,32 @@ public class PipTransitionState { } } + /** + * Posts the state update for PiP in the context of transitions onto the main handler. + * + * <p>This is done to guarantee that any callback dispatches for the present state are + * complete. This is relevant for states that have multiple listeners, such as + * <code>SCHEDULED_BOUNDS_CHANGE</code> that helps turn off touch interactions along with + * the actual transition scheduling.</p> + */ + public void postState(@TransitionState int state) { + postState(state, null /* extra */); + } + + /** + * Posts the state update for PiP in the context of transitions onto the main handler. + * + * <p>This is done to guarantee that any callback dispatches for the present state are + * complete. This is relevant for states that have multiple listeners, such as + * <code>SCHEDULED_BOUNDS_CHANGE</code> that helps turn off touch interactions along with + * the actual transition scheduling.</p> + * + * @param extra a bundle passed to the subscribed listeners to resolve/cache extra info. + */ + public void postState(@TransitionState int state, @Nullable Bundle extra) { + mMainHandler.post(() -> setState(state, extra)); + } + private void dispatchPipTransitionStateChanged(@TransitionState int oldState, @TransitionState int newState, @Nullable Bundle extra) { mCallbacks.forEach(l -> l.onPipTransitionStateChanged(oldState, newState, extra)); @@ -254,22 +299,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/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index c5f23a8ed034..c53e7fe00598 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -50,9 +50,9 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SingleInstanceRemoteListener; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -327,7 +327,8 @@ public class RecentTasksController implements TaskStackListenerCallback, private boolean shouldEnableRunningTasksForDesktopMode() { return mPcFeatureEnabled - || (DesktopModeStatus.isEnabled() && enableDesktopWindowingTaskbarRunningApps()); + || (DesktopModeStatus.canEnterDesktopMode(mContext) + && enableDesktopWindowingTaskbarRunningApps()); } @VisibleForTesting @@ -371,7 +372,8 @@ public class RecentTasksController implements TaskStackListenerCallback, continue; } - if (DesktopModeStatus.isEnabled() && mDesktopModeTaskRepository.isPresent() + if (DesktopModeStatus.canEnterDesktopMode(mContext) + && mDesktopModeTaskRepository.isPresent() && mDesktopModeTaskRepository.get().isActiveTask(taskInfo.taskId)) { if (mDesktopModeTaskRepository.get().isMinimizedTask(taskInfo.taskId)) { // Minimized freeform tasks should not be shown at all. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 6aad4e2c9da4..8df287d12cbc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -69,7 +69,9 @@ public interface SplitScreen { default void onSplitVisibilityChanged(boolean visible) {} } - /** Callback interface for listening to requests to enter split select */ + /** + * Callback interface for listening to requests to enter split select. Used for desktop -> split + */ interface SplitSelectListener { default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo, int splitPosition, Rect taskBounds) { @@ -90,6 +92,24 @@ public interface SplitScreen { /** Unregisters listener that gets split screen callback. */ void unregisterSplitScreenListener(@NonNull SplitScreenListener listener); + interface SplitInvocationListener { + /** + * Called whenever shell starts or stops the split screen animation + * @param animationRunning if {@code true} the animation has begun, if {@code false} the + * animation has finished + */ + default void onSplitAnimationInvoked(boolean animationRunning) { } + } + + /** + * Registers a {@link SplitInvocationListener} to notify when the animation to enter split + * screen has started and stopped + * + * @param executor callbacks to the listener will be executed on this executor + */ + void registerSplitAnimationListener(@NonNull SplitInvocationListener listener, + @NonNull Executor executor); + /** Called when device waking up finished. */ void onFinishedWakingUp(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 547457b018a1..b9d70e1a599d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -1166,6 +1166,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override + public void registerSplitAnimationListener(@NonNull SplitInvocationListener listener, + @NonNull Executor executor) { + mStageCoordinator.registerSplitAnimationListener(listener, executor); + } + + @Override public void onFinishedWakingUp() { mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 1a53a1d10dd2..0541a0287179 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -55,6 +55,7 @@ import com.android.wm.shell.transition.OneShotRemoteHandler; import com.android.wm.shell.transition.Transitions; import java.util.ArrayList; +import java.util.concurrent.Executor; /** Manages transition animations for split-screen. */ class SplitScreenTransitions { @@ -79,6 +80,8 @@ class SplitScreenTransitions { private Transitions.TransitionFinishCallback mFinishCallback = null; private SurfaceControl.Transaction mFinishTransaction; + private SplitScreen.SplitInvocationListener mSplitInvocationListener; + private Executor mSplitInvocationListenerExecutor; SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions, @NonNull Runnable onFinishCallback, StageCoordinator stageCoordinator) { @@ -353,6 +356,10 @@ class SplitScreenTransitions { + " skip to start enter split transition since it already exist. "); return null; } + if (mSplitInvocationListenerExecutor != null && mSplitInvocationListener != null) { + mSplitInvocationListenerExecutor.execute(() -> mSplitInvocationListener + .onSplitAnimationInvoked(true /*animationRunning*/)); + } final IBinder transition = mTransitions.startTransition(transitType, wct, handler); setEnterTransition(transition, remoteTransition, extraTransitType, resizeAnim); return transition; @@ -457,6 +464,7 @@ class SplitScreenTransitions { mPendingEnter.onConsumed(aborted); mPendingEnter = null; + mStageCoordinator.notifySplitAnimationFinished(); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for enter transition"); } else if (isPendingDismiss(transition)) { mPendingDismiss.onConsumed(aborted); @@ -493,10 +501,11 @@ class SplitScreenTransitions { mAnimatingTransition = null; mOnFinish.run(); - if (mFinishCallback != null) { - mFinishCallback.onTransitionFinished(wct /* wct */); - mFinishCallback = null; - } + if (mFinishCallback != null) { + Transitions.TransitionFinishCallback currentFinishCallback = mFinishCallback; + mFinishCallback = null; + currentFinishCallback.onTransitionFinished(wct /* wct */); + } } private void startFadeAnimation(@NonNull SurfaceControl leash, boolean show) { @@ -529,6 +538,12 @@ class SplitScreenTransitions { mTransitions.getAnimExecutor().execute(va::start); } + public void registerSplitAnimListener(@NonNull SplitScreen.SplitInvocationListener listener, + @NonNull Executor executor) { + mSplitInvocationListener = listener; + mSplitInvocationListenerExecutor = executor; + } + /** Calls when the transition got consumed. */ interface TransitionConsumedCallback { void onConsumed(boolean aborted); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 5e9451a09d41..9f8cb625fe17 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -16,10 +16,8 @@ package com.android.wm.shell.splitscreen; -import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED; -import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; @@ -43,6 +41,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString; +import static com.android.wm.shell.common.split.SplitScreenUtils.getResizingBackgroundColor; import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition; import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; @@ -67,6 +66,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN; import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; +import static com.android.wm.shell.transition.MixedTransitionHelper.getPipReplacingChange; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; @@ -157,6 +157,7 @@ import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Executor; /** * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and @@ -237,6 +238,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private DefaultMixedHandler mMixedHandler; private final Toast mSplitUnsupportedToast; private SplitRequest mSplitRequest; + /** Used to notify others of when shell is animating into split screen */ + private SplitScreen.SplitInvocationListener mSplitInvocationListener; + private Executor mSplitInvocationListenerExecutor; /** * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support @@ -247,6 +251,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return false; } + /** NOTE: Will overwrite any previously set {@link #mSplitInvocationListener} */ + public void registerSplitAnimationListener( + @NonNull SplitScreen.SplitInvocationListener listener, @NonNull Executor executor) { + mSplitInvocationListener = listener; + mSplitInvocationListenerExecutor = executor; + mSplitTransitions.registerSplitAnimListener(listener, executor); + } + class SplitRequest { @SplitPosition int mActivatePosition; @@ -535,7 +547,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, null /* childrenToTop */, EXIT_REASON_UNKNOWN)); Log.w(TAG, splitFailureMessage("startShortcut", "side stage was not populated")); - mSplitUnsupportedToast.show(); + handleUnsupportedSplitStart(); } if (finishedCallback != null) { @@ -666,7 +678,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, null /* childrenToTop */, EXIT_REASON_UNKNOWN)); Log.w(TAG, splitFailureMessage("startIntentLegacy", "side stage was not populated")); - mSplitUnsupportedToast.show(); + handleUnsupportedSplitStart(); } if (apps != null) { @@ -1287,7 +1299,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); Log.w(TAG, splitFailureMessage("onRemoteAnimationFinishedOrCancelled", "main or side stage was not populated.")); - mSplitUnsupportedToast.show(); + handleUnsupportedSplitStart(); } else { mSyncQueue.queue(evictWct); mSyncQueue.runInSync(t -> { @@ -1308,7 +1320,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); Log.w(TAG, splitFailureMessage("onRemoteAnimationFinished", "main or side stage was not populated")); - mSplitUnsupportedToast.show(); + handleUnsupportedSplitStart(); return; } @@ -1873,13 +1885,15 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) { + ActivityOptions options = ActivityOptions.fromBundle(opts); if (launchTarget != null) { - opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token); + options.setLaunchRootTask(launchTarget.mRootTaskInfo.token); } // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split // will be canceled. - opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true); - opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true); + options.setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); + opts.putAll(options.toBundle()); } void updateActivityOptions(Bundle opts, @SplitPosition int position) { @@ -2374,14 +2388,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) { + public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY, + boolean shouldUseParallaxEffect) { final SurfaceControl.Transaction t = mTransactionPool.acquire(); t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); - updateSurfaceBounds(layout, t, true /* applyResizingOffset */); + updateSurfaceBounds(layout, t, shouldUseParallaxEffect); getMainStageBounds(mTempRect1); getSideStageBounds(mTempRect2); - mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately); - mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately); + // TODO (b/307490004): "commonColor" below is a temporary fix to ensure the colors on both + // sides match. When b/307490004 is fixed, this code can be reverted. + float[] commonColor = getResizingBackgroundColor(mSideStage.mRootTaskInfo).getComponents(); + mMainStage.onResizing( + mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately, commonColor); + mSideStage.onResizing( + mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately, commonColor); t.apply(); mTransactionPool.release(t); } @@ -2824,7 +2844,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.setFreezeDividerWindow(false); final StageChangeRecord record = new StageChangeRecord(); final int transitType = info.getType(); - boolean hasEnteringPip = false; + TransitionInfo.Change pipChange = null; for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); if (change.getMode() == TRANSIT_CHANGE @@ -2835,7 +2855,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } if (mMixedHandler.isEnteringPip(change, transitType)) { - hasEnteringPip = true; + pipChange = change; } final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -2887,9 +2907,20 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - if (hasEnteringPip) { + if (pipChange != null) { + TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange, + mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId, + getSplitItemStage(pipChange.getLastParent())); + if (pipReplacingChange != null) { + // Set an enter transition for when startAnimation gets called again + mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null, + TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false); + } + mMixedHandler.animatePendingEnterPipFromSplit(transition, info, - startTransaction, finishTransaction, finishCallback); + startTransaction, finishTransaction, finishCallback, + pipReplacingChange != null); + notifySplitAnimationFinished(); return true; } @@ -2924,6 +2955,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // the transition, or synchronize task-org callbacks. } // Use normal animations. + notifySplitAnimationFinished(); return false; } else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) { // A display-change has been un-expectedly inserted into the transition. Redirect @@ -2937,6 +2969,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.update(startTransaction, true /* resetImePosition */); startTransaction.apply(); } + notifySplitAnimationFinished(); return true; } } @@ -3110,7 +3143,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, pendingEnter.mRemoteHandler.onTransitionConsumed(transition, false /*aborted*/, finishT); } - mSplitUnsupportedToast.show(); + handleUnsupportedSplitStart(); return true; } } @@ -3139,6 +3172,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final TransitionInfo.Change finalMainChild = mainChild; final TransitionInfo.Change finalSideChild = sideChild; enterTransition.setFinishedCallback((callbackWct, callbackT) -> { + notifySplitAnimationFinished(); if (finalMainChild != null) { if (!mainNotContainOpenTask) { mMainStage.evictOtherChildren(callbackWct, finalMainChild.getTaskInfo().taskId); @@ -3560,6 +3594,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.isLeftRightSplit()); } + private void handleUnsupportedSplitStart() { + mSplitUnsupportedToast.show(); + notifySplitAnimationFinished(); + } + + void notifySplitAnimationFinished() { + if (mSplitInvocationListener == null || mSplitInvocationListenerExecutor == null) { + return; + } + mSplitInvocationListenerExecutor.execute(() -> + mSplitInvocationListener.onSplitAnimationInvoked(false /*animationRunning*/)); + } + /** * Logs the exit of splitscreen to a specific stage. This must be called before the exit is * executed. @@ -3622,7 +3669,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!ENABLE_SHELL_TRANSITIONS) { StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); - mSplitUnsupportedToast.show(); + handleUnsupportedSplitStart(); return; } @@ -3642,7 +3689,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, "app package " + taskInfo.baseActivity.getPackageName() + " does not support splitscreen, or is a controlled activity type")); if (splitScreenVisible) { - mSplitUnsupportedToast.show(); + handleUnsupportedSplitStart(); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index f41bca36bb70..0f3d6cade95a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -177,9 +177,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: task=%d taskParent=%d rootTask=%d", + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskAppeared: taskId=%d taskParent=%d rootTask=%d " + + "taskActivity=%s", taskInfo.taskId, taskInfo.parentTaskId, - mRootTaskInfo != null ? mRootTaskInfo.taskId : -1); + mRootTaskInfo != null ? mRootTaskInfo.taskId : -1, + taskInfo.baseActivity); if (mRootTaskInfo == null) { mRootLeash = leash; mRootTaskInfo = taskInfo; @@ -213,6 +215,8 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { @Override @CallSuper public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s", + taskInfo.taskId, taskInfo.baseActivity); mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo)); if (mRootTaskInfo.taskId == taskInfo.taskId) { // Inflates split decor view only when the root task is visible. @@ -260,6 +264,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskVanished: task=%d", taskInfo.taskId); final int taskId = taskInfo.taskId; + mWindowDecorViewModel.ifPresent(vm -> vm.onTaskVanished(taskInfo)); if (mRootTaskInfo.taskId == taskId) { mCallbacks.onRootTaskVanished(); mRootTaskInfo = null; @@ -309,10 +314,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX, - int offsetY, boolean immediately) { + int offsetY, boolean immediately, float[] veilColor) { if (mSplitDecorManager != null && mRootTaskInfo != null) { mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX, - offsetY, immediately); + offsetY, immediately, veilColor); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java index e419462012e3..e07e1b460168 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenIconDrawableFactory.java @@ -45,6 +45,7 @@ import android.window.SplashScreenView; import com.android.internal.R; +import java.io.Closeable; import java.util.function.LongConsumer; /** @@ -100,7 +101,7 @@ public class SplashscreenIconDrawableFactory { * Drawable pre-drawing the scaled icon in a separate thread to increase the speed of the * final drawing. */ - private static class ImmobileIconDrawable extends Drawable { + private static class ImmobileIconDrawable extends Drawable implements Closeable { private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG); private final Matrix mMatrix = new Matrix(); @@ -154,6 +155,16 @@ public class SplashscreenIconDrawableFactory { public int getOpacity() { return 1; } + + @Override + public void close() { + synchronized (mPaint) { + if (mIconBitmap != null) { + mIconBitmap.recycle(); + mIconBitmap = null; + } + } + } } /** 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..bcacecbd8981 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 @@ -77,6 +77,7 @@ public class DefaultMixedHandler implements MixedTransitionHandler, private ActivityEmbeddingController mActivityEmbeddingController; abstract static class MixedTransition { + /** Entering Pip from split, breaks split. */ static final int TYPE_ENTER_PIP_FROM_SPLIT = 1; /** Both the display and split-state (enter/exit) is changing */ @@ -103,6 +104,9 @@ public class DefaultMixedHandler implements MixedTransitionHandler, /** Enter pip from one of the Activity Embedding windows. */ static final int TYPE_ENTER_PIP_FROM_ACTIVITY_EMBEDDING = 9; + /** Entering Pip from split, but replace the Pip stage instead of breaking split. */ + static final int TYPE_ENTER_PIP_REPLACE_FROM_SPLIT = 10; + /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; @@ -426,7 +430,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 @@ -483,9 +488,11 @@ public class DefaultMixedHandler implements MixedTransitionHandler, // TODO(b/287704263): Remove when split/mixed are reversed. public boolean animatePendingEnterPipFromSplit(IBinder transition, TransitionInfo info, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - Transitions.TransitionFinishCallback finishCallback) { - final MixedTransition mixed = createDefaultMixedTransition( - MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT, transition); + Transitions.TransitionFinishCallback finishCallback, boolean replacingPip) { + int type = replacingPip + ? MixedTransition.TYPE_ENTER_PIP_REPLACE_FROM_SPLIT + : MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT; + final MixedTransition mixed = createDefaultMixedTransition(type, transition); mActiveTransitions.add(mixed); Transitions.TransitionFinishCallback callback = wct -> { mActiveTransitions.remove(mixed); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java index b028bd65b438..0ada74937df4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java @@ -76,7 +76,12 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { info, startTransaction, finishTransaction, finishCallback); case TYPE_ENTER_PIP_FROM_SPLIT -> animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, - finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler, + /*replacingPip*/ false); + case TYPE_ENTER_PIP_REPLACE_FROM_SPLIT -> + animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler, + /*replacingPip*/ true); case TYPE_KEYGUARD -> animateKeyguard(this, info, startTransaction, finishTransaction, finishCallback, mKeyguardHandler, mPipHandler); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java index ffc0b76b131d..e8b01b5880fb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java @@ -23,11 +23,15 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; +import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; import android.annotation.NonNull; +import android.annotation.Nullable; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -45,7 +49,8 @@ public class MixedTransitionHelper { @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull Transitions player, @NonNull MixedTransitionHandler mixedHandler, - @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) { + @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler, + boolean replacingPip) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for " + "entering PIP while Split-Screen is foreground."); TransitionInfo.Change pipChange = null; @@ -99,7 +104,7 @@ public class MixedTransitionHelper { // we need a separate one to send over to launcher. SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction(); @SplitScreen.StageType int topStageToKeep = STAGE_TYPE_UNDEFINED; - if (splitHandler.isSplitScreenVisible()) { + if (splitHandler.isSplitScreenVisible() && !replacingPip) { // The non-going home case, we could be pip-ing one of the split stages and keep // showing the other for (int i = info.getChanges().size() - 1; i >= 0; --i) { @@ -115,11 +120,12 @@ public class MixedTransitionHelper { break; } } + + // Let split update internal state for dismiss. + splitHandler.prepareDismissAnimation(topStageToKeep, + EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, + finishTransaction); } - // Let split update internal state for dismiss. - splitHandler.prepareDismissAnimation(topStageToKeep, - EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT, - finishTransaction); // We are trying to accommodate launcher's close animation which can't handle the // divider-bar, so if split-handler is closing the divider-bar, just hide it and @@ -152,6 +158,44 @@ public class MixedTransitionHelper { return true; } + /** + * Check to see if we're only closing split to enter pip or if we're replacing pip with + * another task. If we are replacing, this will return the change for the task we are replacing + * pip with + * + * @param info Any number of changes + * @param pipChange TransitionInfo.Change indicating the task that is being pipped + * @param splitMainStageRootId MainStage's rootTaskInfo's id + * @param splitSideStageRootId SideStage's rootTaskInfo's id + * @param lastPipSplitStage The last stage that {@param pipChange} was in + * @return The change from {@param info} that is replacing the {@param pipChange}, {@code null} + * otherwise + */ + @Nullable + public static TransitionInfo.Change getPipReplacingChange(TransitionInfo info, + TransitionInfo.Change pipChange, int splitMainStageRootId, int splitSideStageRootId, + @SplitScreen.StageType int lastPipSplitStage) { + int lastPipParentTask = -1; + if (lastPipSplitStage == STAGE_TYPE_MAIN) { + lastPipParentTask = splitMainStageRootId; + } else if (lastPipSplitStage == STAGE_TYPE_SIDE) { + lastPipParentTask = splitSideStageRootId; + } + + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + TransitionInfo.Change change = info.getChanges().get(i); + if (change == pipChange || !isOpeningMode(change.getMode())) { + // Ignore the change/task that's going into Pip or not opening + continue; + } + + if (change.getTaskInfo().parentTaskId == lastPipParentTask) { + return change; + } + } + return null; + } + private static boolean isHomeOpening(@NonNull TransitionInfo.Change change) { return change.getTaskInfo() != null && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java index d6e64cfaf4d5..9fc6702562bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java @@ -142,7 +142,8 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition { && mSplitHandler.getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) { return animateEnterPipFromSplit(this, info, startTransaction, finishTransaction, - finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler); + finishCallback, mPlayer, mMixedHandler, mPipHandler, mSplitHandler, + /*replacingPip*/ false); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 9b2922dff2f5..6224543516fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -31,7 +31,6 @@ import static android.view.WindowManager.fixScale; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; -import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; import static android.window.TransitionInfo.FLAG_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; @@ -61,6 +60,7 @@ import android.view.SurfaceControl; import android.view.WindowManager; import android.window.ITransitionPlayer; import android.window.RemoteTransition; +import android.window.TaskFragmentOrganizer; import android.window.TransitionFilter; import android.window.TransitionInfo; import android.window.TransitionMetrics; @@ -183,6 +183,13 @@ public class Transitions implements RemoteCallable<Transitions>, /** Transition to resize PiP task. */ public static final int TRANSIT_RESIZE_PIP = TRANSIT_FIRST_CUSTOM + 16; + /** + * The task fragment drag resize transition used by activity embedding. + */ + public static final int TRANSIT_TASK_FRAGMENT_DRAG_RESIZE = + // TRANSIT_FIRST_CUSTOM + 17 + TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE; + private final ShellTaskOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; @@ -559,15 +566,15 @@ public class Transitions implements RemoteCallable<Transitions>, final int mode = change.getMode(); // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { - if (isOpening - // This is for when an activity launches while a different transition is - // collecting. - || change.hasFlags(FLAG_MOVED_TO_TOP)) { + if (isOpening) { // put on top return zSplitLine + numChanges - i; - } else { + } else if (isClosing) { // put on bottom return zSplitLine - i; + } else { + // maintain relative ordering (put all changes in the animating layer) + return zSplitLine + numChanges - i; } } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) { if (isOpening) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java index 1897560deed7..6adbe4f7ce92 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java @@ -49,8 +49,12 @@ public class PerfettoTransitionTracer implements TransitionTracer { public PerfettoTransitionTracer() { Producer.init(InitArguments.DEFAULTS); - mDataSource.register( - new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .build(); + mDataSource.register(params); } /** @@ -214,8 +218,6 @@ public class PerfettoTransitionTracer implements TransitionTracer { } os.end(mappingsToken); - - ctx.flush(); }); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 87dc3915082f..bfa163cb2860 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -63,6 +63,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final SyncTransactionQueue mSyncQueue; private final Transitions mTransitions; + private final ResizeHandleSizeRepository mResizeHandleSizeRepository; private TaskOperations mTaskOperations; private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>(); @@ -75,7 +76,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SyncTransactionQueue syncQueue, - Transitions transitions) { + Transitions transitions, + ResizeHandleSizeRepository resizeHandleSizeRepository) { mContext = context; mMainHandler = mainHandler; mMainChoreographer = mainChoreographer; @@ -84,6 +86,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mSyncQueue = syncQueue; mTransitions = transitions; + mResizeHandleSizeRepository = resizeHandleSizeRepository; if (!Transitions.ENABLE_SHELL_TRANSITIONS) { mTaskOperations = new TaskOperations(null, mContext, mSyncQueue); } @@ -119,6 +122,21 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { } @Override + public void onTaskVanished(RunningTaskInfo taskInfo) { + // A task vanishing doesn't necessarily mean the task was closed, it could also mean its + // windowing mode changed. We're only interested in closing tasks so checking whether + // its info still exists in the task organizer is one way to disambiguate. + final boolean closed = mTaskOrganizer.getRunningTaskInfo(taskInfo.taskId) == null; + if (closed) { + // Destroying the window decoration is usually handled when a TRANSIT_CLOSE transition + // changes happen, but there are certain cases in which closing tasks aren't included + // in transitions, such as when a non-visible task is closed. See b/296921167. + // Destroy the decoration here in case the lack of transition missed it. + destroyWindowDecoration(taskInfo); + } + } + + @Override public void onTaskChanging( RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -216,7 +234,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { taskSurface, mMainHandler, mMainChoreographer, - mSyncQueue); + mSyncQueue, + mResizeHandleSizeRepository); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); final FluidResizeTaskPositioner taskPositioner = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 6671391efdeb..8a49a73e0253 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -16,11 +16,11 @@ package com.android.wm.shell.windowdecor; -import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; -import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; -import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; +import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getFineResizeCornerPixels; +import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getLargeResizeCornerPixels; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; @@ -45,6 +45,8 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; +import java.util.function.Consumer; + /** * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with * {@link CaptionWindowDecorViewModel}. The caption bar contains a back button, minimize button, @@ -58,12 +60,28 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL private View.OnClickListener mOnCaptionButtonClickListener; private View.OnTouchListener mOnCaptionTouchListener; private DragPositioningCallback mDragPositioningCallback; + // Listener for handling drag resize events. Will be null if the task cannot be resized. + @Nullable private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; private RelayoutParams mRelayoutParams = new RelayoutParams(); private final RelayoutResult<WindowDecorLinearLayout> mResult = new RelayoutResult<>(); + private final ResizeHandleSizeRepository mResizeHandleSizeRepository; + private final Consumer<ResizeHandleSizeRepository> mResizeHandleSizeChangedFunction = + (ResizeHandleSizeRepository sizeRepository) -> { + if (mDragResizeListener == null) { + return; + } + final Resources res = mResult.mRootView.getResources(); + mDragResizeListener.setGeometry( + new DragResizeWindowGeometry(0 /* taskCornerRadius */, + new Size(mResult.mWidth, mResult.mHeight), + sizeRepository.getResizeEdgeHandlePixels(res), + getFineResizeCornerPixels(res), getLargeResizeCornerPixels(res)), + mDragDetector.getTouchSlop()); + }; CaptionWindowDecoration( Context context, @@ -73,13 +91,15 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL SurfaceControl taskSurface, Handler handler, Choreographer choreographer, - SyncTransactionQueue syncQueue) { - super(context, displayController, taskOrganizer, taskInfo, taskSurface, - taskInfo.getConfiguration()); + SyncTransactionQueue syncQueue, + ResizeHandleSizeRepository resizeHandleSizeRepository) { + super(context, displayController, taskOrganizer, taskInfo, taskSurface); mHandler = handler; mChoreographer = choreographer; mSyncQueue = syncQueue; + mResizeHandleSizeRepository = resizeHandleSizeRepository; + mResizeHandleSizeRepository.registerSizeChangeFunction(mResizeHandleSizeChangedFunction); } void setCaptionListeners( @@ -238,10 +258,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL .getScaledTouchSlop(); mDragDetector.setTouchSlop(touchSlop); - final Resources res = mResult.mRootView.getResources(); - mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */, - new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res), - getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop); + mResizeHandleSizeChangedFunction.accept(mResizeHandleSizeRepository); } /** 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 01175f598089..10ab13a74042 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 @@ -35,6 +35,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.compatui.AppCompatUtils.isSingleTopActivityTranslucent; import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR; +import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import android.annotation.NonNull; @@ -72,6 +73,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; @@ -82,12 +84,12 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreen.StageType; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -147,6 +149,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new DesktopModeKeyguardChangeListener(); private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; private final DisplayInsetsController mDisplayInsetsController; + private final ResizeHandleSizeRepository mResizeHandleSizeRepository; private final Region mExclusionRegion = Region.obtain(); private boolean mInImmersiveMode; @@ -179,7 +182,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + ResizeHandleSizeRepository resizeHandleSizeRepository ) { this( context, @@ -200,7 +204,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { new InputMonitorFactory(), SurfaceControl.Transaction::new, rootTaskDisplayAreaOrganizer, - new SparseArray<>()); + new SparseArray<>(), + resizeHandleSizeRepository); } @VisibleForTesting @@ -223,7 +228,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, - SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId) { + SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId, + ResizeHandleSizeRepository resizeHandleSizeRepository) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -244,6 +250,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mInputManager = mContext.getSystemService(InputManager.class); mWindowDecorByTaskId = windowDecorByTaskId; + mResizeHandleSizeRepository = resizeHandleSizeRepository; shellInit.addInitCallback(this::onInit, this); } @@ -276,9 +283,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.isEnabled()) { - mDesktopTasksController.moveToSplit(decor.mTaskInfo); + if (decor == null || !DesktopModeStatus.canEnterDesktopMode(mContext) + || decor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM) { + return; } + mDesktopTasksController.moveToSplit(decor.mTaskInfo); } } }); @@ -309,6 +318,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } @Override + public void onTaskVanished(RunningTaskInfo taskInfo) { + // A task vanishing doesn't necessarily mean the task was closed, it could also mean its + // windowing mode changed. We're only interested in closing tasks so checking whether + // its info still exists in the task organizer is one way to disambiguate. + final boolean closed = mTaskOrganizer.getRunningTaskInfo(taskInfo.taskId) == null; + ProtoLog.v(WM_SHELL_DESKTOP_MODE, "Task Vanished: #%d closed=%b", taskInfo.taskId, closed); + if (closed) { + // Destroying the window decoration is usually handled when a TRANSIT_CLOSE transition + // changes happen, but there are certain cases in which closing tasks aren't included + // in transitions, such as when a non-visible task is closed. See b/296921167. + // Destroy the decoration here in case the lack of transition missed it. + destroyWindowDecoration(taskInfo); + } + } + + @Override public void onTaskChanging( RunningTaskInfo taskInfo, SurfaceControl taskSurface, @@ -594,7 +619,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { public boolean handleMotionEvent(@Nullable View v, MotionEvent e) { final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId); final RunningTaskInfo taskInfo = decoration.mTaskInfo; - if (DesktopModeStatus.isEnabled() + if (DesktopModeStatus.canEnterDesktopMode(mContext) && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { return false; } @@ -771,7 +796,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { */ private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) { final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev); - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(mContext)) { if (!mInImmersiveMode && (relevantDecor == null || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM || mTransitionDragActive)) { @@ -780,7 +805,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } handleEventOutsideCaption(ev, relevantDecor); // Prevent status bar from reacting to a caption drag. - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(mContext)) { if (mTransitionDragActive) { inputMonitor.pilferPointers(); } @@ -838,7 +863,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragToDesktopAnimationStartBounds.set( relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds()); boolean dragFromStatusBarAllowed = false; - if (DesktopModeStatus.isEnabled()) { + if (DesktopModeStatus.canEnterDesktopMode(mContext)) { // In proto2 any full screen or multi-window task can be dragged to // freeform. final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode(); @@ -1013,12 +1038,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { && isSingleTopActivityTranslucent(taskInfo)) { return false; } - return DesktopModeStatus.isEnabled() + return DesktopModeStatus.canEnterDesktopMode(mContext) && !DesktopWallpaperActivity.isWallpaperTask(taskInfo) && taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD - && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop() - && DesktopModeStatus.canEnterDesktopMode(mContext); + && !taskInfo.configuration.windowConfiguration.isAlwaysOnTop(); } private void createWindowDecoration( @@ -1041,7 +1065,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMainHandler, mMainChoreographer, mSyncQueue, - mRootTaskDisplayAreaOrganizer); + mRootTaskDisplayAreaOrganizer, + mResizeHandleSizeRepository); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); final DragPositioningCallback dragPositioningCallback; @@ -1087,7 +1112,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private void dump(PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + "DesktopModeWindowDecorViewModel"); - pw.println(innerPrefix + "DesktopModeStatus=" + DesktopModeStatus.isEnabled()); + pw.println(innerPrefix + "DesktopModeStatus=" + + DesktopModeStatus.canEnterDesktopMode(mContext)); pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive); pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay); pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId); 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 8c6bc73d10bf..9c92791f255f 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 @@ -24,11 +24,11 @@ import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; -import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize; -import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize; -import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize; +import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getFineResizeCornerPixels; +import static com.android.wm.shell.windowdecor.ResizeHandleSizeRepository.getLargeResizeCornerPixels; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; @@ -60,14 +60,14 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.IconProvider; +import com.android.window.flags.Flags; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.desktopmode.DesktopModeStatus; -import com.android.wm.shell.desktopmode.DesktopTasksController; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.DesktopModeAppControlsWindowDecorationViewHolder; import com.android.wm.shell.windowdecor.viewholder.DesktopModeFocusedWindowDecorationViewHolder; @@ -75,6 +75,7 @@ import com.android.wm.shell.windowdecor.viewholder.DesktopModeWindowDecorationVi import kotlin.Unit; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -96,6 +97,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private View.OnLongClickListener mOnCaptionLongClickListener; private View.OnGenericMotionListener mOnCaptionGenericMotionListener; private DragPositioningCallback mDragPositioningCallback; + // Listener for handling drag resize events. Will be null if the task cannot be resized. + @Nullable private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; @@ -110,28 +113,43 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private ResizeVeil mResizeVeil; private Bitmap mAppIconBitmap; + private Bitmap mResizeVeilBitmap; + private CharSequence mAppName; private ExclusionRegionListener mExclusionRegionListener; private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final ResizeHandleSizeRepository mResizeHandleSizeRepository; + private final Function<ResizeHandleSizeRepository, Boolean> mResizeHandleSizeChangedFunction = + (ResizeHandleSizeRepository sizeRepository) -> { + final Resources res = mResult.mRootView.getResources(); + return mDragResizeListener == null || mDragResizeListener.setGeometry( + new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius, + new Size(mResult.mWidth, mResult.mHeight), + sizeRepository.getResizeEdgeHandlePixels(res), + getFineResizeCornerPixels(res), + getLargeResizeCornerPixels(res)), + mDragDetector.getTouchSlop()); + }; + DesktopModeWindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, - Configuration windowDecorConfig, Handler handler, Choreographer choreographer, SyncTransactionQueue syncQueue, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { - this (context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + ResizeHandleSizeRepository resizeHandleSizeRepository) { + this (context, displayController, taskOrganizer, taskInfo, taskSurface, handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer, - SurfaceControl.Builder::new, SurfaceControl.Transaction::new, - WindowContainerTransaction::new, SurfaceControl::new, - new SurfaceControlViewHostFactory() {}); + resizeHandleSizeRepository, SurfaceControl.Builder::new, + SurfaceControl.Transaction::new, WindowContainerTransaction::new, + SurfaceControl::new, new SurfaceControlViewHostFactory() {}); } DesktopModeWindowDecoration( @@ -140,17 +158,17 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, - Configuration windowDecorConfig, Handler handler, Choreographer choreographer, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + ResizeHandleSizeRepository resizeHandleSizeRepository, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory) { - super(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig, + super(context, displayController, taskOrganizer, taskInfo, taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, surfaceControlViewHostFactory); @@ -158,6 +176,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mChoreographer = choreographer; mSyncQueue = syncQueue; mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + mResizeHandleSizeRepository = resizeHandleSizeRepository; + mResizeHandleSizeRepository.registerSizeChangeFunction( + mResizeHandleSizeChangedFunction::apply); } void setCaptionListeners( @@ -303,11 +324,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // If either task geometry or position have changed, update this task's // exclusion region listener - final Resources res = mResult.mRootView.getResources(); - if (mDragResizeListener.setGeometry( - new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius, - new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res), - getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop) + if (mResizeHandleSizeChangedFunction.apply(mResizeHandleSizeRepository) || !mTaskInfo.positionInParent.equals(mPositionInParent)) { updateExclusionRegion(); } @@ -330,13 +347,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode()); + final boolean isAppHeader = + captionLayoutId == R.layout.desktop_mode_app_controls_window_decor; + final boolean isAppHandle = captionLayoutId == R.layout.desktop_mode_focused_window_decor; relayoutParams.reset(); relayoutParams.mRunningTaskInfo = taskInfo; relayoutParams.mLayoutResId = captionLayoutId; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId); - if (captionLayoutId == R.layout.desktop_mode_app_controls_window_decor) { + if (isAppHeader) { if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { // If the app is requesting to customize the caption bar, allow input to fall // through to the windows below so that the app can respond to input events on @@ -357,7 +377,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end; controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END; relayoutParams.mOccludingCaptionElements.add(controlsElement); - } else if (captionLayoutId == R.layout.desktop_mode_focused_window_decor) { + } else if (isAppHandle) { // The focused decor (fullscreen/split) does not need to handle input because input in // the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel. relayoutParams.mInputFeatures @@ -370,19 +390,25 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop; - // The configuration used to lay out the window decoration. The system context's config is - // used when the task density has been overridden to a custom density so that the resources - // and views of the decoration aren't affected and match the rest of the System UI, if not - // then just use the task's configuration. A copy is made instead of using the original - // reference so that the configuration isn't mutated on config changes and diff checks can - // be made in WindowDecoration#relayout using the pre/post-relayout configuration. - // See b/301119301. + + // The configuration used to layout the window decoration. A copy is made instead of using + // the original reference so that the configuration isn't mutated on config changes and + // diff checks can be made in WindowDecoration#relayout using the pre/post-relayout + // configuration. See b/301119301. // TODO(b/301119301): consider moving the config data needed for diffs to relayout params // instead of using a whole Configuration as a parameter. final Configuration windowDecorConfig = new Configuration(); - windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet() - ? context.getResources().getConfiguration() // Use system context. - : taskInfo.configuration); // Use task configuration. + if (Flags.enableAppHeaderWithTaskDensity() && isAppHeader) { + // Should match the density of the task. The task may have had its density overridden + // to be different that SysUI's. + windowDecorConfig.setTo(taskInfo.configuration); + } else if (DesktopModeStatus.isDesktopDensityOverrideSet()) { + // The task has had its density overridden, but keep using the system's density to + // layout the header. + windowDecorConfig.setTo(context.getResources().getConfiguration()); + } else { + windowDecorConfig.setTo(taskInfo.configuration); + } relayoutParams.mWindowDecorConfig = windowDecorConfig; if (DesktopModeStatus.useRoundedCorners()) { @@ -468,11 +494,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 +510,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 +532,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); } @@ -638,7 +675,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin .setOnClickListener(mOnCaptionButtonClickListener) .setOnTouchListener(mOnCaptionTouchListener) .setLayoutId(mRelayoutParams.mLayoutResId) - .setWindowingButtonsVisible(DesktopModeStatus.isEnabled()) + .setWindowingButtonsVisible(DesktopModeStatus.canEnterDesktopMode(mContext)) .setCaptionHeight(mResult.mCaptionHeight) .build(); mWindowDecorViewHolder.onHandleMenuOpened(); @@ -923,22 +960,19 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Handler handler, Choreographer choreographer, SyncTransactionQueue syncQueue, - RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) { - final Configuration windowDecorConfig = - DesktopTasksController.isDesktopDensityOverrideSet() - ? context.getResources().getConfiguration() // Use system context - : taskInfo.configuration; // Use task configuration + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + ResizeHandleSizeRepository resizeHandleSizeRepository) { return new DesktopModeWindowDecoration( context, displayController, taskOrganizer, taskInfo, taskSurface, - windowDecorConfig, handler, choreographer, syncQueue, - rootTaskDisplayAreaOrganizer); + rootTaskDisplayAreaOrganizer, + resizeHandleSizeRepository); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java index da268988bac7..a3b0a7122752 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java @@ -119,6 +119,10 @@ class DragDetector { mTouchSlop = touchSlop; } + int getTouchSlop() { + return mTouchSlop; + } + private void resetState() { mIsDragEvent = false; mInputDownPoint.set(0, 0); 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..80d60d4fbdd0 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 @@ -26,14 +26,14 @@ import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED; import android.annotation.NonNull; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; import android.util.Size; import android.view.MotionEvent; -import com.android.wm.shell.R; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import java.util.Objects; @@ -41,6 +41,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,11 +56,14 @@ 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; + /** + * Constructs an instance representing the drag resize touch input regions, where all sizes + * are represented in pixels. + */ DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize, int resizeHandleThickness, int fineCornerSize, int largeCornerSize) { mTaskCornerRadius = taskCornerRadius; @@ -66,51 +74,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); - } - - /** - * Returns the resource value to use for the resize handle on the edge of the window. - */ - static int getResizeEdgeHandleSize(@NonNull Resources res) { - return enableWindowingEdgeDragResize() - ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle) - : res.getDimensionPixelSize(R.dimen.freeform_resize_handle); - } - - /** - * Returns the resource value to use for course input, such as touch, that benefits from a large - * square on each of the window's corners. - */ - static int getLargeResizeCornerSize(@NonNull Resources res) { - return res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large); - } - - /** - * Returns the resource value to use for fine input, such as stylus, that can use a smaller - * square on each of the window's corners. - */ - static int getFineResizeCornerSize(@NonNull Resources res) { - return res.getDimensionPixelSize(R.dimen.freeform_resize_corner); + mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness); + if (DEBUG) { + mDebugTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness + EDGE_DEBUG_BUFFER); + } else { + mDebugTaskEdges = null; + } } /** @@ -127,10 +96,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 +188,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 +282,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 +295,11 @@ final class DragResizeWindowGeometry { mResizeHandleThickness, mFineTaskCorners, mLargeTaskCorners, - mTopEdgeBounds, - mLeftEdgeBounds, - mRightEdgeBounds, - mBottomEdgeBounds); + (inDebugMode() ? mDebugTaskEdges : mTaskEdges)); + } + + private boolean inDebugMode() { + return DEBUG && mDebugTaskEdges != null; } /** @@ -431,4 +407,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/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt index 899b7cc0ea0d..22f0adc42f5d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt @@ -16,6 +16,9 @@ package com.android.wm.shell.windowdecor +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.ValueAnimator import android.annotation.IdRes import android.app.ActivityManager.RunningTaskInfo import android.content.Context @@ -30,16 +33,21 @@ import android.view.SurfaceControlViewHost import android.view.View.OnClickListener import android.view.View.OnGenericMotionListener import android.view.View.OnTouchListener +import android.view.View.SCALE_Y +import android.view.View.TRANSLATION_Y +import android.view.View.TRANSLATION_Z import android.view.WindowManager import android.view.WindowlessWindowManager import android.widget.Button import android.widget.FrameLayout import android.widget.LinearLayout +import android.widget.TextView import android.window.TaskConstants import androidx.core.content.withStyledAttributes import com.android.internal.R.attr.colorAccentPrimary import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.windowdecor.WindowDecoration.AdditionalWindow @@ -65,14 +73,13 @@ class MaximizeMenu( private var maximizeMenu: AdditionalWindow? = null private lateinit var viewHost: SurfaceControlViewHost private lateinit var leash: SurfaceControl - private val shadowRadius = loadDimensionPixelSize( - R.dimen.desktop_mode_maximize_menu_shadow_radius - ).toFloat() + private val openMenuAnimatorSet = AnimatorSet() private val cornerRadius = loadDimensionPixelSize( R.dimen.desktop_mode_maximize_menu_corner_radius ).toFloat() private val menuWidth = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_width) private val menuHeight = loadDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_height) + private val menuPadding = loadDimensionPixelSize(R.dimen.desktop_mode_menu_padding) private lateinit var snapRightButton: Button private lateinit var snapLeftButton: Button @@ -91,10 +98,12 @@ class MaximizeMenu( if (maximizeMenu != null) return createMaximizeMenu() setupMaximizeMenu() + animateOpenMenu() } /** Closes the maximize window and releases its view. */ fun close() { + openMenuAnimatorSet.cancel() maximizeMenu?.releaseView() maximizeMenu = null } @@ -134,8 +143,6 @@ class MaximizeMenu( // Bring menu to front when open t.setLayer(leash, TaskConstants.TASK_CHILD_LAYER_FLOATING_MENU) .setPosition(leash, menuPosition.x, menuPosition.y) - .setWindowCrop(leash, menuWidth, menuHeight) - .setShadowRadius(leash, shadowRadius) .setCornerRadius(leash, cornerRadius) .show(leash) maximizeMenu = AdditionalWindow(leash, viewHost, transactionSupplier) @@ -146,6 +153,77 @@ class MaximizeMenu( } } + private fun animateOpenMenu() { + val viewHost = maximizeMenu?.mWindowViewHost + val maximizeMenuView = viewHost?.view ?: return + val maximizeWindowText = maximizeMenuView.requireViewById<TextView>( + R.id.maximize_menu_maximize_window_text) + val snapWindowText = maximizeMenuView.requireViewById<TextView>( + R.id.maximize_menu_snap_window_text) + + openMenuAnimatorSet.playTogether( + ObjectAnimator.ofFloat(maximizeMenuView, SCALE_Y, STARTING_MENU_HEIGHT_SCALE, 1f) + .apply { + duration = MENU_HEIGHT_ANIMATION_DURATION_MS + interpolator = EMPHASIZED_DECELERATE + }, + ValueAnimator.ofFloat(STARTING_MENU_HEIGHT_SCALE, 1f) + .apply { + duration = MENU_HEIGHT_ANIMATION_DURATION_MS + interpolator = EMPHASIZED_DECELERATE + addUpdateListener { + // Animate padding so that controls stay pinned to the bottom of + // the menu. + val value = animatedValue as Float + val topPadding = menuPadding - + ((1 - value) * menuHeight).toInt() + maximizeMenuView.setPadding(menuPadding, topPadding, + menuPadding, menuPadding) + } + }, + ValueAnimator.ofFloat(1 / STARTING_MENU_HEIGHT_SCALE, 1f).apply { + duration = MENU_HEIGHT_ANIMATION_DURATION_MS + interpolator = EMPHASIZED_DECELERATE + addUpdateListener { + // Scale up the children of the maximize menu so that the menu + // scale is cancelled out and only the background is scaled. + val value = animatedValue as Float + maximizeButtonLayout.scaleY = value + snapButtonsLayout.scaleY = value + maximizeWindowText.scaleY = value + snapWindowText.scaleY = value + } + }, + ObjectAnimator.ofFloat(maximizeMenuView, TRANSLATION_Y, + (STARTING_MENU_HEIGHT_SCALE - 1) * menuHeight, 0f).apply { + duration = MENU_HEIGHT_ANIMATION_DURATION_MS + interpolator = EMPHASIZED_DECELERATE + }, + ObjectAnimator.ofInt(maximizeMenuView.background, "alpha", + MAX_DRAWABLE_ALPHA_VALUE).apply { + duration = ALPHA_ANIMATION_DURATION_MS + }, + ValueAnimator.ofFloat(0f, 1f) + .apply { + duration = ALPHA_ANIMATION_DURATION_MS + startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS + addUpdateListener { + val value = animatedValue as Float + maximizeButtonLayout.alpha = value + snapButtonsLayout.alpha = value + maximizeWindowText.alpha = value + snapWindowText.alpha = value + } + }, + ObjectAnimator.ofFloat(maximizeMenuView, TRANSLATION_Z, MENU_Z_TRANSLATION) + .apply { + duration = ELEVATION_ANIMATION_DURATION_MS + startDelay = CONTROLS_ALPHA_ANIMATION_DELAY_MS + } + ) + openMenuAnimatorSet.start() + } + private fun loadDimensionPixelSize(resourceId: Int): Int { return if (resourceId == Resources.ID_NULL) { 0 @@ -263,6 +341,14 @@ class MaximizeMenu( } companion object { + // Open menu animation constants + private const val ALPHA_ANIMATION_DURATION_MS = 50L + private const val MAX_DRAWABLE_ALPHA_VALUE = 255 + private const val STARTING_MENU_HEIGHT_SCALE = 0.8f + private const val MENU_HEIGHT_ANIMATION_DURATION_MS = 300L + private const val ELEVATION_ANIMATION_DURATION_MS = 50L + private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L + private const val MENU_Z_TRANSLATION = 1f fun isMaximizeMenuView(@IdRes viewId: Int): Boolean { return viewId == R.id.maximize_menu || viewId == R.id.maximize_menu_maximize_button || diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt new file mode 100644 index 000000000000..be7a301ec4c3 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepository.kt @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.windowdecor + +import android.content.res.Resources +import com.android.window.flags.Flags.enableWindowingEdgeDragResize +import com.android.wm.shell.R +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.util.KtProtoLog +import java.util.function.Consumer + +/** Repository for desktop mode drag resize handle sizes. */ +class ResizeHandleSizeRepository { + private val TAG = "ResizeHandleSizeRepository" + private var edgeResizeHandleSizePixels: Int? = null + private var sizeChangeFunctions: MutableList<Consumer<ResizeHandleSizeRepository>> = + mutableListOf() + + /** + * Resets the window edge resize handle size if necessary. + */ + fun resetResizeEdgeHandlePixels() { + if (enableWindowingEdgeDragResize() && edgeResizeHandleSizePixels != null) { + edgeResizeHandleSizePixels = null + applyToAll() + } + } + + /** + * Sets the window edge resize handle to the given size in pixels. + */ + fun setResizeEdgeHandlePixels(sizePixels: Int) { + if (enableWindowingEdgeDragResize()) { + KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "$TAG: Set edge handle size to $sizePixels") + if (edgeResizeHandleSizePixels != null && edgeResizeHandleSizePixels == sizePixels) { + // Skip updating since override is the same size + return + } + edgeResizeHandleSizePixels = sizePixels + applyToAll() + } else { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "$TAG: Can't set edge handle size to $sizePixels since " + + "enable_windowing_edge_drag_resize disabled" + ) + } + } + + /** + * Returns the resource value, in pixels, to use for the resize handle on the edge of the + * window. + */ + fun getResizeEdgeHandlePixels(res: Resources): Int { + try { + return if (enableWindowingEdgeDragResize()) { + val resPixelSize = res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle) + val size = edgeResizeHandleSizePixels ?: resPixelSize + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "$TAG: Get edge handle size of $size from (vs base value $resPixelSize)" + ) + size + } else { + KtProtoLog.d( + WM_SHELL_DESKTOP_MODE, + "$TAG: Get edge handle size from freeform since flag is disabled" + ) + res.getDimensionPixelSize(R.dimen.freeform_resize_handle) + } + } catch (e: Resources.NotFoundException) { + KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get edge handle size", e) + return 0 + } + } + + /** Register function to run when the resize handle size changes. */ + fun registerSizeChangeFunction(function: Consumer<ResizeHandleSizeRepository>) { + sizeChangeFunctions.add(function) + } + + private fun applyToAll() { + for (f in sizeChangeFunctions) { + f.accept(this) + } + } + + companion object { + private val TAG = "ResizeHandleSizeRepositoryCompanion" + + /** + * Returns the resource value in pixels to use for course input, such as touch, that + * benefits from a large square on each of the window's corners. + */ + @JvmStatic + fun getLargeResizeCornerPixels(res: Resources): Int { + return try { + res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large) + } catch (e: Resources.NotFoundException) { + KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get large corner size", e) + 0 + } + } + + /** + * Returns the resource value, in pixels, to use for fine input, such as stylus, that can + * use a smaller square on each of the window's corners. + */ + @JvmStatic + fun getFineResizeCornerPixels(res: Resources): Int { + return try { + res.getDimensionPixelSize(R.dimen.freeform_resize_corner) + } catch (e: Resources.NotFoundException) { + KtProtoLog.e(WM_SHELL_DESKTOP_MODE, "$TAG: Unable to get fine corner size", e) + 0 + } + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java index 01a6012ea314..1563259f4a1a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java @@ -67,6 +67,14 @@ public interface WindowDecorViewModel { void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo); /** + * Notifies a task has vanished, which can mean that the task changed windowing mode or was + * removed. + * + * @param taskInfo the task info of the task + */ + void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo); + + /** * Notifies a transition is about to start about the given task to give the window decoration a * chance to prepare for this transition. Unlike {@link #onTaskInfoChanged}, this method creates * a window decoration if one does not exist but is required. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index de6c03549f0e..2cbe47212c63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -18,6 +18,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED; import static android.view.WindowInsets.Type.captionBar; import static android.view.WindowInsets.Type.mandatorySystemGestures; import static android.view.WindowInsets.Type.statusBars; @@ -52,7 +53,7 @@ import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement; import java.util.ArrayList; @@ -145,9 +146,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, - SurfaceControl taskSurface, - Configuration windowDecorConfig) { - this(context, displayController, taskOrganizer, taskInfo, taskSurface, windowDecorConfig, + SurfaceControl taskSurface) { + this(context, displayController, taskOrganizer, taskInfo, taskSurface, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, new SurfaceControlViewHostFactory() {}); @@ -159,7 +159,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, @NonNull SurfaceControl taskSurface, - Configuration windowDecorConfig, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, @@ -176,8 +175,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); - mWindowDecorConfig = windowDecorConfig; - mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig); } /** @@ -220,8 +217,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> outResult.mRootView = rootView; rootView = null; // Clear it just in case we use it accidentally - final int oldDensityDpi = mWindowDecorConfig.densityDpi; - final int oldNightMode = mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; + final int oldDensityDpi = mWindowDecorConfig != null + ? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED; + final int oldNightMode = mWindowDecorConfig != null + ? (mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) + : Configuration.UI_MODE_NIGHT_UNDEFINED; mWindowDecorConfig = params.mWindowDecorConfig != null ? params.mWindowDecorConfig : mTaskInfo.getConfiguration(); final int newDensityDpi = mWindowDecorConfig.densityDpi; @@ -230,7 +230,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> || mDisplay == null || mDisplay.getDisplayId() != mTaskInfo.displayId || oldLayoutResId != mLayoutResId - || oldNightMode != newNightMode) { + || oldNightMode != newNightMode + || mDecorWindowContext == null) { releaseViews(wct); if (!obtainDisplayOrRegisterListener()) { 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/OWNERS b/libs/WindowManager/Shell/tests/OWNERS index 0f24bb549158..b8a19ad35307 100644 --- a/libs/WindowManager/Shell/tests/OWNERS +++ b/libs/WindowManager/Shell/tests/OWNERS @@ -13,3 +13,5 @@ nmusgrave@google.com pbdr@google.com tkachenkoi@google.com mpodolian@google.com +jeremysim@google.com +peanutbutter@google.com diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml index 4dd14f4011d0..f69a90cc793f 100644 --- a/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/appcompat/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml index 5c86a386fc6c..b76d06565700 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/bubble/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt index bc486c277aa5..984abf8cf8b4 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt @@ -32,7 +32,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTests:MultiBubblesScreen` + * To run this test: `atest WMShellFlickerTestsBubbles:ChangeActiveActivityFromBubbleTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt index 2a9b1078afe3..886b70c5e464 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/DragToDismissBubbleScreenTest.kt @@ -35,7 +35,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTests:DismissBubbleScreen` + * To run this test: `atest WMShellFlickerTestsBubbles:DragToDismissBubbleScreenTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt index 9ef49c1c9e7e..2ee53f4fce66 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt @@ -38,7 +38,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTests:OpenActivityFromBubbleOnLocksreenTest` + * To run this test: `atest WMShellFlickerTestsBubbles:OpenActivityFromBubbleOnLocksreenTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt index ef7fbfb79beb..463fe0e60da3 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleTest.kt @@ -29,7 +29,7 @@ import org.junit.runners.Parameterized /** * Test launching a new activity from bubble. * - * To run this test: `atest WMShellFlickerTests:ExpandBubbleScreen` + * To run this test: `atest WMShellFlickerTestsBubbles:OpenActivityFromBubbleTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt index 87224b151b78..8df50567a29c 100644 --- a/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/bubble/src/com/android/wm/shell/flicker/bubble/SendBubbleNotificationTest.kt @@ -29,7 +29,7 @@ import org.junit.runners.Parameterized /** * Test creating a bubble notification * - * To run this test: `atest WMShellFlickerTests:LaunchBubbleScreen` + * To run this test: `atest WMShellFlickerTestsBubbles:SendBubbleNotificationTest` * * Actions: * ``` diff --git a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml index aa70c093b847..041978c371ff 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml index c7c804f2361a..a66dfb4566f9 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/service/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt index 17cace0da739..d485b82f5ddb 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/flicker/DesktopModeFlickerScenarios.kt @@ -21,6 +21,7 @@ import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart import android.tools.flicker.assertors.assertions.AppWindowHasDesktopModeInitialBoundsAtTheEnd +import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways import android.tools.flicker.assertors.assertions.AppWindowOnTopAtEnd import android.tools.flicker.assertors.assertions.AppWindowOnTopAtStart import android.tools.flicker.assertors.assertions.AppWindowRemainInsideDisplayBounds @@ -133,9 +134,8 @@ class DesktopModeFlickerScenarios { } ), assertions = - AssertionTemplates.COMMON_ASSERTIONS + listOf( - AppLayerIsVisibleAlways(Components.DESKTOP_MODE_APP), + AppWindowIsVisibleAlways(Components.DESKTOP_MODE_APP), AppWindowOnTopAtEnd(Components.DESKTOP_MODE_APP), AppWindowRemainInsideDisplayBounds(Components.DESKTOP_MODE_APP), ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }), diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt index 0c2b5015840d..e77a45729124 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/CloseAllAppsWithAppHeaderExit.kt @@ -55,7 +55,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { @Before fun setup() { - Assume.assumeTrue(Flags.enableDesktopWindowingMode()) + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) testApp.enterDesktopWithDrag(wmHelper, device) diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt index 9e9998ef7c2a..fe139d2d24a0 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/EnterDesktopWithDrag.kt @@ -50,7 +50,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { @Before fun setup() { - Assume.assumeTrue(Flags.enableDesktopWindowingMode()) + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) } diff --git a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt index 289ca9f5d92e..ac9089a5c1bd 100644 --- a/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt +++ b/libs/WindowManager/Shell/tests/flicker/service/src/com/android/wm/shell/flicker/service/desktopmode/scenarios/ResizeAppWithCornerResize.kt @@ -50,7 +50,7 @@ constructor(val rotation: Rotation = Rotation.ROTATION_0) { @Before fun setup() { - Assume.assumeTrue(Flags.enableDesktopWindowingMode()) + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) tapl.setEnableRotation(true) tapl.setExpectedRotation(rotation.value) testApp.enterDesktopWithDrag(wmHelper, device) diff --git a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml index 214bdfaa0743..85715db3d952 100644 --- a/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/splitscreen/AndroidTestTemplate.xml @@ -91,6 +91,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java index 2ac72affbb0c..ea522cdf2509 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java @@ -20,6 +20,8 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; +import static com.android.wm.shell.transition.Transitions.TRANSIT_TASK_FRAGMENT_DRAG_RESIZE; + import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -100,6 +102,20 @@ public class ActivityEmbeddingAnimationRunnerTests extends ActivityEmbeddingAnim } @Test + public void testTransitionTypeDragResize() { + final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TASK_FRAGMENT_DRAG_RESIZE, 0) + .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) + .build(); + final Animator animator = mAnimRunner.createAnimator( + info, mStartTransaction, mFinishTransaction, + () -> mFinishCallback.onTransitionFinished(null /* wct */), + new ArrayList()); + + // The animation should be empty when it is a jump cut for drag resize. + assertEquals(0, animator.getDuration()); + } + + @Test public void testInvalidCustomAnimation() { final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) .addChange(createChange(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index f99b4b2beef0..f6f3aa49bc6e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -120,7 +120,7 @@ public class BackAnimationControllerTest extends ShellTestCase { private TestableContentResolver mContentResolver; private TestableLooper mTestableLooper; - private CrossActivityBackAnimation mCrossActivityBackAnimation; + private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation; private CrossTaskBackAnimation mCrossTaskBackAnimation; private ShellBackAnimationRegistry mShellBackAnimationRegistry; @@ -135,13 +135,14 @@ public class BackAnimationControllerTest extends ShellTestCase { ANIMATION_ENABLED); mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); - mCrossActivityBackAnimation = new CrossActivityBackAnimation(mContext, mAnimationBackground, - mRootTaskDisplayAreaOrganizer); + mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext, + mAnimationBackground, mRootTaskDisplayAreaOrganizer); mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground); mShellBackAnimationRegistry = - new ShellBackAnimationRegistry(mCrossActivityBackAnimation, mCrossTaskBackAnimation, - /* dialogCloseAnimation= */ null, - new CustomizeActivityAnimation(mContext, mAnimationBackground), + new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation, + mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null, + new CustomCrossActivityBackAnimation(mContext, mAnimationBackground, + mRootTaskDisplayAreaOrganizer), /* defaultBackToHomeAnimation= */ null); mController = new BackAnimationController( @@ -582,7 +583,7 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void testBackToActivity() throws RemoteException { verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, - mCrossActivityBackAnimation.getRunner()); + mDefaultCrossActivityBackAnimation.getRunner()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt new file mode 100644 index 000000000000..8bf011192347 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt @@ -0,0 +1,264 @@ +/* + * 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.f + */ +package com.android.wm.shell.back + +import android.app.ActivityManager +import android.app.ActivityManager.RunningTaskInfo +import android.app.AppCompatTaskInfo +import android.app.WindowConfiguration +import android.graphics.Color +import android.graphics.Point +import android.graphics.Rect +import android.os.RemoteException +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.Choreographer +import android.view.RemoteAnimationTarget +import android.view.SurfaceControl +import android.view.SurfaceControl.Transaction +import android.view.animation.Animation +import android.window.BackEvent +import android.window.BackMotionEvent +import android.window.BackNavigationInfo +import androidx.test.filters.SmallTest +import com.android.internal.policy.TransitionAnimation +import com.android.wm.shell.RootTaskDisplayAreaOrganizer +import com.android.wm.shell.ShellTestCase +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import junit.framework.TestCase.assertEquals +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyFloat +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever + +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class CustomCrossActivityBackAnimationTest : ShellTestCase() { + @Mock private lateinit var backAnimationBackground: BackAnimationBackground + @Mock private lateinit var mockCloseAnimation: Animation + @Mock private lateinit var mockOpenAnimation: Animation + @Mock private lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock private lateinit var transitionAnimation: TransitionAnimation + @Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo + @Mock private lateinit var transaction: Transaction + + private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation + private lateinit var customAnimationLoader: CustomAnimationLoader + + @Before + @Throws(Exception::class) + fun setUp() { + customAnimationLoader = CustomAnimationLoader(transitionAnimation) + customCrossActivityBackAnimation = + CustomCrossActivityBackAnimation( + context, + backAnimationBackground, + rootTaskDisplayAreaOrganizer, + transaction, + mock(Choreographer::class.java), + customAnimationLoader + ) + + whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID))) + .thenReturn(mockOpenAnimation) + whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(CLOSE_RES_ID))) + .thenReturn(mockCloseAnimation) + whenever(transaction.setColor(any(), any())).thenReturn(transaction) + whenever(transaction.setAlpha(any(), anyFloat())).thenReturn(transaction) + whenever(transaction.setCrop(any(), any())).thenReturn(transaction) + whenever(transaction.setRelativeLayer(any(), any(), anyInt())).thenReturn(transaction) + spy(customCrossActivityBackAnimation) + } + + @Test + @Throws(InterruptedException::class) + fun receiveFinishAfterInvoke() { + val finishCalled = startCustomAnimation() + try { + customCrossActivityBackAnimation.getRunner().callback.onBackInvoked() + } catch (r: RemoteException) { + Assert.fail("onBackInvoked throw remote exception") + } + finishCalled.await(1, TimeUnit.SECONDS) + } + + @Test + @Throws(InterruptedException::class) + fun receiveFinishAfterCancel() { + val finishCalled = startCustomAnimation() + try { + customCrossActivityBackAnimation.getRunner().callback.onBackCancelled() + } catch (r: RemoteException) { + Assert.fail("onBackCancelled throw remote exception") + } + finishCalled.await(1, TimeUnit.SECONDS) + } + + @Test + @Throws(InterruptedException::class) + fun receiveFinishWithoutAnimationAfterInvoke() { + val finishCalled = startCustomAnimation(targets = arrayOf()) + try { + customCrossActivityBackAnimation.getRunner().callback.onBackInvoked() + } catch (r: RemoteException) { + Assert.fail("onBackInvoked throw remote exception") + } + finishCalled.await(1, TimeUnit.SECONDS) + } + + @Test + fun testLoadCustomAnimation() { + testLoadCustomAnimation(OPEN_RES_ID, CLOSE_RES_ID, 0) + } + + @Test + fun testLoadCustomAnimationNoEnter() { + testLoadCustomAnimation(0, CLOSE_RES_ID, 0) + } + + @Test + fun testLoadWindowAnimations() { + testLoadCustomAnimation(0, 0, 30) + } + + @Test + fun testCustomAnimationHigherThanWindowAnimations() { + testLoadCustomAnimation(OPEN_RES_ID, CLOSE_RES_ID, 30) + } + + private fun testLoadCustomAnimation(enterResId: Int, exitResId: Int, windowAnimations: Int) { + val builder = + BackNavigationInfo.Builder() + .setCustomAnimation(PACKAGE_NAME, enterResId, exitResId, Color.GREEN) + .setWindowAnimations(PACKAGE_NAME, windowAnimations) + val info = builder.build().customAnimationInfo!! + whenever( + transitionAnimation.loadAnimationAttr( + eq(PACKAGE_NAME), + eq(windowAnimations), + anyInt(), + anyBoolean() + ) + ) + .thenReturn(mockCloseAnimation) + whenever(transitionAnimation.loadDefaultAnimationAttr(anyInt(), anyBoolean())) + .thenReturn(mockOpenAnimation) + val result = customAnimationLoader.loadAll(info)!! + if (exitResId != 0) { + if (enterResId == 0) { + verify(transitionAnimation, never()) + .loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(enterResId)) + verify(transitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean()) + } else { + assertEquals(result.enterAnimation, mockOpenAnimation) + } + assertEquals(result.backgroundColor.toLong(), Color.GREEN.toLong()) + assertEquals(result.closeAnimation, mockCloseAnimation) + verify(transitionAnimation, never()) + .loadAnimationAttr(eq(PACKAGE_NAME), anyInt(), anyInt(), anyBoolean()) + } else if (windowAnimations != 0) { + verify(transitionAnimation, times(2)) + .loadAnimationAttr(eq(PACKAGE_NAME), anyInt(), anyInt(), anyBoolean()) + Assert.assertEquals(result.closeAnimation, mockCloseAnimation) + } + } + + private fun startCustomAnimation( + targets: Array<RemoteAnimationTarget> = + arrayOf(createAnimationTarget(false), createAnimationTarget(true)) + ): CountDownLatch { + val backNavigationInfo = + BackNavigationInfo.Builder() + .setCustomAnimation(PACKAGE_NAME, OPEN_RES_ID, CLOSE_RES_ID, /*backgroundColor*/ 0) + .build() + customCrossActivityBackAnimation.prepareNextAnimation( + backNavigationInfo.customAnimationInfo, + 0 + ) + val finishCalled = CountDownLatch(1) + val finishCallback = Runnable { finishCalled.countDown() } + customCrossActivityBackAnimation + .getRunner() + .startAnimation(targets, null, null, finishCallback) + customCrossActivityBackAnimation.runner.callback.onBackStarted(backMotionEventFrom(0f, 0f)) + if (targets.isNotEmpty()) { + verify(mockCloseAnimation) + .initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE)) + verify(mockOpenAnimation) + .initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE), eq(BOUND_SIZE)) + } + return finishCalled + } + + private fun backMotionEventFrom(touchX: Float, progress: Float) = + BackMotionEvent( + /* touchX = */ touchX, + /* touchY = */ 0f, + /* progress = */ progress, + /* velocityX = */ 0f, + /* velocityY = */ 0f, + /* triggerBack = */ false, + /* swipeEdge = */ BackEvent.EDGE_LEFT, + /* departingAnimationTarget = */ null + ) + + private fun createAnimationTarget(open: Boolean): RemoteAnimationTarget { + val topWindowLeash = SurfaceControl() + val taskInfo = RunningTaskInfo() + taskInfo.appCompatTaskInfo = appCompatTaskInfo + taskInfo.taskDescription = ActivityManager.TaskDescription() + return RemoteAnimationTarget( + 1, + if (open) RemoteAnimationTarget.MODE_OPENING else RemoteAnimationTarget.MODE_CLOSING, + topWindowLeash, + false, + Rect(), + Rect(), + -1, + Point(0, 0), + Rect(0, 0, BOUND_SIZE, BOUND_SIZE), + Rect(), + WindowConfiguration(), + true, + null, + null, + taskInfo, + false, + -1 + ) + } + + companion object { + private const val BOUND_SIZE = 100 + private const val OPEN_RES_ID = 1000 + private const val CLOSE_RES_ID = 1001 + private const val PACKAGE_NAME = "TestPackage" + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java deleted file mode 100644 index 158d640dca30..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.back; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -import android.app.WindowConfiguration; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.RemoteException; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.Choreographer; -import android.view.RemoteAnimationTarget; -import android.view.SurfaceControl; -import android.view.animation.Animation; -import android.window.BackNavigationInfo; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@SmallTest -@TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner.class) -public class CustomizeActivityAnimationTest extends ShellTestCase { - private static final int BOUND_SIZE = 100; - @Mock - private BackAnimationBackground mBackAnimationBackground; - @Mock - private Animation mMockCloseAnimation; - @Mock - private Animation mMockOpenAnimation; - - private CustomizeActivityAnimation mCustomizeActivityAnimation; - - @Before - public void setUp() throws Exception { - mCustomizeActivityAnimation = new CustomizeActivityAnimation(mContext, - mBackAnimationBackground, mock(SurfaceControl.Transaction.class), - mock(Choreographer.class)); - spyOn(mCustomizeActivityAnimation); - spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation); - } - - RemoteAnimationTarget createAnimationTarget(boolean open) { - SurfaceControl topWindowLeash = new SurfaceControl(); - return new RemoteAnimationTarget(1, - open ? RemoteAnimationTarget.MODE_OPENING : RemoteAnimationTarget.MODE_CLOSING, - topWindowLeash, false, new Rect(), new Rect(), -1, - new Point(0, 0), new Rect(0, 0, BOUND_SIZE, BOUND_SIZE), new Rect(), - new WindowConfiguration(), true, null, null, null, false, -1); - } - - @Test - public void receiveFinishAfterInvoke() throws InterruptedException { - spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader); - doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) - .loadAnimation(any(), eq(false)); - doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) - .loadAnimation(any(), eq(true)); - - mCustomizeActivityAnimation.prepareNextAnimation( - new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0); - final RemoteAnimationTarget close = createAnimationTarget(false); - final RemoteAnimationTarget open = createAnimationTarget(true); - // start animation with remote animation targets - final CountDownLatch finishCalled = new CountDownLatch(1); - final Runnable finishCallback = finishCalled::countDown; - mCustomizeActivityAnimation - .getRunner() - .startAnimation( - new RemoteAnimationTarget[] {close, open}, null, null, finishCallback); - verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), - eq(BOUND_SIZE), eq(BOUND_SIZE)); - verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), - eq(BOUND_SIZE), eq(BOUND_SIZE)); - - try { - mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked(); - } catch (RemoteException r) { - fail("onBackInvoked throw remote exception"); - } - verify(mCustomizeActivityAnimation).onGestureCommitted(); - finishCalled.await(1, TimeUnit.SECONDS); - } - - @Test - public void receiveFinishAfterCancel() throws InterruptedException { - spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader); - doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) - .loadAnimation(any(), eq(false)); - doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader) - .loadAnimation(any(), eq(true)); - - mCustomizeActivityAnimation.prepareNextAnimation( - new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0); - final RemoteAnimationTarget close = createAnimationTarget(false); - final RemoteAnimationTarget open = createAnimationTarget(true); - // start animation with remote animation targets - final CountDownLatch finishCalled = new CountDownLatch(1); - final Runnable finishCallback = finishCalled::countDown; - mCustomizeActivityAnimation - .getRunner() - .startAnimation( - new RemoteAnimationTarget[] {close, open}, null, null, finishCallback); - verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), - eq(BOUND_SIZE), eq(BOUND_SIZE)); - verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE), - eq(BOUND_SIZE), eq(BOUND_SIZE)); - - try { - mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled(); - } catch (RemoteException r) { - fail("onBackCancelled throw remote exception"); - } - finishCalled.await(1, TimeUnit.SECONDS); - } - - @Test - public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException { - mCustomizeActivityAnimation.prepareNextAnimation( - new BackNavigationInfo.CustomAnimationInfo("TestPackage"), 0); - // start animation without any remote animation targets - final CountDownLatch finishCalled = new CountDownLatch(1); - final Runnable finishCallback = finishCalled::countDown; - mCustomizeActivityAnimation - .getRunner() - .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback); - - try { - mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked(); - } catch (RemoteException r) { - fail("onBackInvoked throw remote exception"); - } - verify(mCustomizeActivityAnimation).onGestureCommitted(); - finishCalled.await(1, TimeUnit.SECONDS); - } - - @Test - public void testLoadCustomAnimation() { - testLoadCustomAnimation(10, 20, 0); - } - - @Test - public void testLoadCustomAnimationNoEnter() { - testLoadCustomAnimation(0, 10, 0); - } - - @Test - public void testLoadWindowAnimations() { - testLoadCustomAnimation(0, 0, 30); - } - - @Test - public void testCustomAnimationHigherThanWindowAnimations() { - testLoadCustomAnimation(10, 20, 30); - } - - private void testLoadCustomAnimation(int enterResId, int exitResId, int windowAnimations) { - final String testPackage = "TestPackage"; - BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() - .setCustomAnimation(testPackage, enterResId, exitResId, Color.GREEN) - .setWindowAnimations(testPackage, windowAnimations); - final BackNavigationInfo.CustomAnimationInfo info = builder.build() - .getCustomAnimationInfo(); - - doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader - .mTransitionAnimation) - .loadAppTransitionAnimation(eq(testPackage), eq(enterResId)); - doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader - .mTransitionAnimation) - .loadAppTransitionAnimation(eq(testPackage), eq(exitResId)); - doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader - .mTransitionAnimation) - .loadAnimationAttr(eq(testPackage), eq(windowAnimations), anyInt(), anyBoolean()); - doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader - .mTransitionAnimation).loadDefaultAnimationAttr(anyInt(), anyBoolean()); - - CustomizeActivityAnimation.AnimationLoadResult result = - mCustomizeActivityAnimation.mCustomAnimationLoader.loadAll(info); - - if (exitResId != 0) { - if (enterResId == 0) { - verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, - never()).loadAppTransitionAnimation(eq(testPackage), eq(enterResId)); - verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation) - .loadDefaultAnimationAttr(anyInt(), anyBoolean()); - } else { - assertEquals(result.mEnterAnimation, mMockOpenAnimation); - } - assertEquals(result.mBackgroundColor, Color.GREEN); - assertEquals(result.mCloseAnimation, mMockCloseAnimation); - verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, never()) - .loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean()); - } else if (windowAnimations != 0) { - verify(mCustomizeActivityAnimation.mCustomAnimationLoader.mTransitionAnimation, - times(2)).loadAnimationAttr(eq(testPackage), anyInt(), anyInt(), anyBoolean()); - assertEquals(result.mCloseAnimation, mMockCloseAnimation); - } - } -} 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/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 56d0f8e13f08..cfe8e07aa6e5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -26,6 +26,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_STA 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.eq; import static org.mockito.Mockito.never; @@ -115,27 +116,27 @@ public class SplitLayoutTests extends ShellTestCase { @Test public void testUpdateDivideBounds() { - mSplitLayout.updateDivideBounds(anyInt()); + mSplitLayout.updateDividerBounds(anyInt(), anyBoolean()); verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(), - anyInt()); + anyInt(), anyBoolean()); } @Test public void testSetDividePosition() { - mSplitLayout.setDividePosition(100, false /* applyLayoutChange */); - assertThat(mSplitLayout.getDividePosition()).isEqualTo(100); + mSplitLayout.setDividerPosition(100, false /* applyLayoutChange */); + assertThat(mSplitLayout.getDividerPosition()).isEqualTo(100); verify(mSplitLayoutHandler, never()).onLayoutSizeChanged(any(SplitLayout.class)); - mSplitLayout.setDividePosition(200, true /* applyLayoutChange */); - assertThat(mSplitLayout.getDividePosition()).isEqualTo(200); + mSplitLayout.setDividerPosition(200, true /* applyLayoutChange */); + assertThat(mSplitLayout.getDividerPosition()).isEqualTo(200); verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class)); } @Test public void testSetDivideRatio() { - mSplitLayout.setDividePosition(200, false /* applyLayoutChange */); + mSplitLayout.setDividerPosition(200, false /* applyLayoutChange */); mSplitLayout.setDivideRatio(SNAP_TO_50_50); - assertThat(mSplitLayout.getDividePosition()).isEqualTo( + assertThat(mSplitLayout.getDividerPosition()).isEqualTo( mSplitLayout.mDividerSnapAlgorithm.getMiddleTarget().position); } @@ -152,7 +153,7 @@ public class SplitLayoutTests extends ShellTestCase { DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, SNAP_TO_START_AND_DISMISS); - mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget); + mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget); waitDividerFlingFinished(); verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt()); } @@ -164,7 +165,7 @@ public class SplitLayoutTests extends ShellTestCase { DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */, SNAP_TO_END_AND_DISMISS); - mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget); + mSplitLayout.snapToTarget(mSplitLayout.getDividerPosition(), snapTarget); waitDividerFlingFinished(); verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt()); } @@ -188,7 +189,7 @@ public class SplitLayoutTests extends ShellTestCase { } private void waitDividerFlingFinished() { - verify(mSplitLayout).flingDividePosition(anyInt(), anyInt(), anyInt(), + verify(mSplitLayout).flingDividerPosition(anyInt(), anyInt(), anyInt(), mRunnableCaptor.capture()); mRunnableCaptor.getValue().run(); } 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/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java index 5209d0e4bdd0..41a81c1a9921 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java @@ -22,6 +22,7 @@ import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_A import static android.app.CameraCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED; import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.WindowInsets.Type.navigationBars; +import static android.view.WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -98,14 +99,28 @@ public class CompatUIWindowManagerTest extends ShellTestCase { private CompatUIWindowManager mWindowManager; private TaskInfo mTaskInfo; + private DisplayLayout mDisplayLayout; @Before public void setUp() { MockitoAnnotations.initMocks(this); doReturn(100).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance(); mTaskInfo = createTaskInfo(/* hasSizeCompat= */ false, CAMERA_COMPAT_CONTROL_HIDDEN); + + final DisplayInfo displayInfo = new DisplayInfo(); + displayInfo.logicalWidth = TASK_WIDTH; + displayInfo.logicalHeight = TASK_HEIGHT; + mDisplayLayout = new DisplayLayout(displayInfo, + mContext.getResources(), /* hasNavigationBar= */ true, /* hasStatusBar= */ false); + final InsetsState insetsState = new InsetsState(); + insetsState.setDisplayFrame(new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT)); + final InsetsSource insetsSource = new InsetsSource( + InsetsSource.createId(null, 0, navigationBars()), navigationBars()); + insetsSource.setFrame(0, TASK_HEIGHT - 200, TASK_WIDTH, TASK_HEIGHT); + insetsState.addSource(insetsSource); + mDisplayLayout.setInsets(mContext.getResources(), insetsState); mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, - mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(), mCompatUIConfiguration, mOnRestartButtonClicked); spyOn(mWindowManager); @@ -363,9 +378,9 @@ public class CompatUIWindowManagerTest extends ShellTestCase { // Update if the insets change on the existing display layout clearInvocations(mWindowManager); - InsetsState insetsState = new InsetsState(); + final InsetsState insetsState = new InsetsState(); insetsState.setDisplayFrame(new Rect(0, 0, 1000, 2000)); - InsetsSource insetsSource = new InsetsSource( + final InsetsSource insetsSource = new InsetsSource( InsetsSource.createId(null, 0, navigationBars()), navigationBars()); insetsSource.setFrame(0, 1800, 1000, 2000); insetsState.addSource(insetsSource); @@ -493,16 +508,14 @@ public class CompatUIWindowManagerTest extends ShellTestCase { @Test public void testShouldShowSizeCompatRestartButton() { mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON); - - doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance(); + doReturn(85).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance(); mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue, - mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(), + mCallback, mTaskListener, mDisplayLayout, new CompatUIHintsState(), mCompatUIConfiguration, mOnRestartButtonClicked); // Simulate rotation of activity in square display TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN); - taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000)); - taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000; + taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = TASK_HEIGHT; taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850; assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); @@ -512,11 +525,21 @@ public class CompatUIWindowManagerTest extends ShellTestCase { assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); // Simulate folding - taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000)); - assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); + final InsetsState insetsState = new InsetsState(); + insetsState.setDisplayFrame(new Rect(0, 0, 1000, TASK_HEIGHT)); + final InsetsSource insetsSource = new InsetsSource( + InsetsSource.createId(null, 0, navigationBars()), navigationBars()); + insetsSource.setFrame(0, TASK_HEIGHT - 200, 1000, TASK_HEIGHT); + insetsState.addSource(insetsSource); + mDisplayLayout.setInsets(mContext.getResources(), insetsState); + mWindowManager.updateDisplayLayout(mDisplayLayout); + taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP - 100; + assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); - taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000; - taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500; + // Simulate floating app with 90& area, more than tolerance + taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; + taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 950; + taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1900; assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo)); } @@ -529,10 +552,10 @@ public class CompatUIWindowManagerTest extends ShellTestCase { cameraCompatControlState; taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK; // Letterboxed activity that takes half the screen should show size compat restart button - taskInfo.configuration.windowConfiguration.setBounds( - new Rect(0, 0, TASK_WIDTH, TASK_HEIGHT)); taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 1000; taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000; + // Screen width dp larger than a normal phone. + taskInfo.configuration.smallestScreenWidthDp = LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP; return taskInfo; } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index 65117f7e9eea..2a2483df0792 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN +import android.content.Context import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.SurfaceControl @@ -35,10 +36,12 @@ import android.window.TransitionInfo import android.window.TransitionInfo.Change import android.window.WindowContainerToken import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions @@ -58,6 +61,11 @@ import org.mockito.kotlin.never import org.mockito.kotlin.same import org.mockito.kotlin.times +/** + * Test class for {@link DesktopModeLoggerTransitionObserver} + * + * Usage: atest WMShellUnitTests:DesktopModeLoggerTransitionObserverTest + */ @SmallTest @RunWith(AndroidTestingRunner::class) class DesktopModeLoggerTransitionObserverTest { @@ -74,6 +82,8 @@ class DesktopModeLoggerTransitionObserverTest { private lateinit var mockShellInit: ShellInit @Mock private lateinit var transitions: Transitions + @Mock + private lateinit var context: Context private lateinit var transitionObserver: DesktopModeLoggerTransitionObserver private lateinit var shellInit: ShellInit @@ -81,12 +91,12 @@ class DesktopModeLoggerTransitionObserverTest { @Before fun setup() { - Mockito.`when`(DesktopModeStatus.isEnabled()).thenReturn(true) + doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) } shellInit = Mockito.spy(ShellInit(testExecutor)) desktopModeEventLogger = mock(DesktopModeEventLogger::class.java) transitionObserver = DesktopModeLoggerTransitionObserver( - mockShellInit, transitions, desktopModeEventLogger) + context, mockShellInit, transitions, desktopModeEventLogger) if (Transitions.ENABLE_SHELL_TRANSITIONS) { val initRunnableCaptor = ArgumentCaptor.forClass( Runnable::class.java) 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/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt new file mode 100644 index 000000000000..285e5b6a04a5 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.desktopmode + + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.Companion.DesktopUiEventEnum.DESKTOP_WINDOW_EDGE_DRAG_RESIZE +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Test class for [DesktopModeUiEventLogger] + * + * Usage: atest WMShellUnitTests:DesktopModeUiEventLoggerTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DesktopModeUiEventLoggerTest : ShellTestCase() { + private lateinit var uiEventLoggerFake: UiEventLoggerFake + private lateinit var logger: DesktopModeUiEventLogger + private val instanceIdSequence = InstanceIdSequence(10) + + + @Before + fun setUp() { + uiEventLoggerFake = UiEventLoggerFake() + logger = DesktopModeUiEventLogger(uiEventLoggerFake, instanceIdSequence) + } + + @Test + fun log_invalidUid_eventNotLogged() { + logger.log(-1, PACKAGE_NAME, DESKTOP_WINDOW_EDGE_DRAG_RESIZE) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } + + @Test + fun log_emptyPackageName_eventNotLogged() { + logger.log(UID, "", DESKTOP_WINDOW_EDGE_DRAG_RESIZE) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } + + @Test + fun log_eventLogged() { + val event = + DESKTOP_WINDOW_EDGE_DRAG_RESIZE + logger.log(UID, PACKAGE_NAME, event) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id) + assertThat(uiEventLoggerFake[0].instanceId).isNull() + assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID) + assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME) + } + + @Test + fun getNewInstanceId() { + val first = logger.getNewInstanceId() + assertThat(first).isNotEqualTo(logger.getNewInstanceId()) + } + + @Test + fun logWithInstanceId_invalidUid_eventNotLogged() { + logger.logWithInstanceId(INSTANCE_ID, -1, PACKAGE_NAME, DESKTOP_WINDOW_EDGE_DRAG_RESIZE) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } + + @Test + fun logWithInstanceId_emptyPackageName_eventNotLogged() { + logger.logWithInstanceId(INSTANCE_ID, UID, "", DESKTOP_WINDOW_EDGE_DRAG_RESIZE) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(0) + } + + @Test + fun logWithInstanceId_eventLogged() { + val event = + DESKTOP_WINDOW_EDGE_DRAG_RESIZE + logger.logWithInstanceId(INSTANCE_ID, UID, PACKAGE_NAME, event) + assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1) + assertThat(uiEventLoggerFake.eventId(0)).isEqualTo(event.id) + assertThat(uiEventLoggerFake[0].instanceId).isEqualTo(INSTANCE_ID) + assertThat(uiEventLoggerFake[0].uid).isEqualTo(UID) + assertThat(uiEventLoggerFake[0].packageName).isEqualTo(PACKAGE_NAME) + } + + + companion object { + private val INSTANCE_ID = InstanceId.fakeInstanceId(0) + private const val UID = 10 + private const val PACKAGE_NAME = "com.foo" + } +} 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 ad4b720facd7..d8d534bec6ea 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,13 @@ 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.CONFIG_DENSITY +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 @@ -73,6 +80,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplit import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -101,11 +109,15 @@ 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 import java.util.Optional +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertTrue import org.mockito.Mockito.`when` as whenever /** @@ -141,6 +153,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,13 +167,23 @@ 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) .spyStatic(DesktopModeStatus::class.java).startMocking() 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) @@ -463,6 +486,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)!! @@ -896,40 +1048,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) - - val freeformTask1 = setUpFreeformTask() - markTaskVisible(freeformTask1) - - val freeformTask2 = createFreeformTask() - assertThat(controller.handleRequest(Binder(), createTransition(freeformTask2))).isNull() - } - - @Test fun handleRequest_freeformTask_freeformVisible_aboveTaskLimit_minimize() { assumeTrue(ENABLE_SHELL_TRANSITIONS) @@ -946,7 +1064,7 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun handleRequest_freeformTask_freeformNotVisible_returnSwitchToFullscreenWCT() { + fun handleRequest_freeformTask_freeformNotVisible_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) val freeformTask1 = setUpFreeformTask() @@ -958,51 +1076,60 @@ class DesktopTasksControllerTest : ShellTestCase() { Binder(), createTransition(freeformTask2, type = TRANSIT_TO_FRONT) ) - assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN + + assertThat(result?.hierarchyOps?.size).isEqualTo(2) + result!!.assertReorderAt(1, freeformTask2, toTop = true) } @Test - fun handleRequest_freeformTask_noOtherTasks_returnSwitchToFullscreenWCT() { + fun handleRequest_freeformTask_noOtherTasks_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) val task = createFreeformTask() val result = controller.handleRequest(Binder(), createTransition(task)) - assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN + + assertThat(result?.hierarchyOps?.size).isEqualTo(1) + result!!.assertReorderAt(0, task, toTop = true) } @Test - fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_returnSwitchToFullscreenWCT() { + fun handleRequest_freeformTask_freeformOnOtherDisplayOnly_reorderedToTop() { assumeTrue(ENABLE_SHELL_TRANSITIONS) val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY) - createFreeformTask(displayId = SECOND_DISPLAY) + val taskSecondDisplay = createFreeformTask(displayId = SECOND_DISPLAY) val result = controller.handleRequest(Binder(), createTransition(taskDefaultDisplay)) - assertThat(result?.changes?.get(taskDefaultDisplay.token.asBinder())?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) // inherited FULLSCREEN + assertThat(result?.hierarchyOps?.size).isEqualTo(1) + result!!.assertReorderAt(0, taskDefaultDisplay, toTop = true) } @Test - @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY) - fun handleRequest_freeformTask_desktopStashed_returnWCTWithAllAppsBroughtToFront() { + fun handleRequest_freeformTask_alreadyInDesktop_noOverrideDensity_noConfigDensityChange() { assumeTrue(ENABLE_SHELL_TRANSITIONS) - whenever(DesktopModeStatus.isStashingEnabled()).thenReturn(true) + whenever(DesktopModeStatus.isDesktopDensityOverrideSet()).thenReturn(false) - val stashedFreeformTask = setUpFreeformTask(DEFAULT_DISPLAY) - markTaskHidden(stashedFreeformTask) + val freeformTask1 = setUpFreeformTask() + markTaskVisible(freeformTask1) - val freeformTask = createFreeformTask(DEFAULT_DISPLAY) + val freeformTask2 = createFreeformTask() + val result = controller.handleRequest(freeformTask2.token.asBinder(), + createTransition(freeformTask2)) + assertFalse(result.anyDensityConfigChange(freeformTask2.token)) + } - controller.stashDesktopApps(DEFAULT_DISPLAY) + @Test + fun handleRequest_freeformTask_alreadyInDesktop_overrideDensity_hasConfigDensityChange() { + assumeTrue(ENABLE_SHELL_TRANSITIONS) + whenever(DesktopModeStatus.isDesktopDensityOverrideSet()).thenReturn(true) - val result = controller.handleRequest(Binder(), createTransition(freeformTask)) - assertThat(result).isNotNull() - result?.assertReorderSequence(stashedFreeformTask, freeformTask) + val freeformTask1 = setUpFreeformTask() + markTaskVisible(freeformTask1) - // Stashed state should be cleared - assertThat(desktopModeTaskRepository.isStashed(DEFAULT_DISPLAY)).isFalse() + val freeformTask2 = createFreeformTask() + val result = controller.handleRequest(freeformTask2.token.asBinder(), + createTransition(freeformTask2)) + assertTrue(result.anyDensityConfigChange(freeformTask2.token)) } @Test @@ -1121,29 +1248,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) @@ -1224,6 +1328,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) @@ -1275,8 +1558,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 @@ -1303,8 +1585,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 @@ -1345,18 +1626,67 @@ 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) - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + 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) - doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true) whenever(splitScreenController.isTaskInSplitScreen(task.taskId)).thenReturn(true) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) @@ -1419,6 +1749,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) { @@ -1430,6 +1771,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()) @@ -1488,3 +1833,11 @@ private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT) assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component) } + +private fun WindowContainerTransaction?.anyDensityConfigChange( + token: WindowContainerToken +): Boolean { + return this?.changes?.any { change -> + change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0) + } ?: false +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 38ea03471b07..3c488cac6edd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -27,10 +27,12 @@ import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.StaticMockitoSession import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions import com.android.wm.shell.util.StubTransaction @@ -41,6 +43,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.any import org.mockito.Mockito.`when` import org.mockito.quality.Strictness @@ -69,7 +72,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun setUp() { mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT) .spyStatic(DesktopModeStatus::class.java).startMocking() - `when`(DesktopModeStatus.isEnabled()).thenReturn(true) + doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(any()) } desktopTaskRepo = DesktopModeTaskRepository() diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java index 71eea4bb59b1..cd68c6996578 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java @@ -19,11 +19,12 @@ package com.android.wm.shell.freeform; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.app.ActivityManager; @@ -34,8 +35,8 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -72,8 +73,10 @@ public final class FreeformTaskListenerTests extends ShellTestCase { public void setup() { mMockitoSession = mockitoSession().initMocks(this) .strictness(Strictness.LENIENT).mockStatic(DesktopModeStatus.class).startMocking(); - when(DesktopModeStatus.isEnabled()).thenReturn(true); + doReturn(true).when(() -> DesktopModeStatus.canEnterDesktopMode(any())); + mFreeformTaskListener = new FreeformTaskListener( + mContext, mShellInit, mTaskOrganizer, Optional.of(mDesktopModeTaskRepository), diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java index bd8ac379b86f..f3f3c37b645d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java @@ -17,6 +17,7 @@ package com.android.wm.shell.pip2; import android.os.Bundle; +import android.os.Handler; import android.os.Parcelable; import android.testing.AndroidTestingRunner; @@ -29,6 +30,7 @@ import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; /** * Unit test against {@link PhoneSizeSpecSource}. @@ -42,9 +44,12 @@ public class PipTransitionStateTest extends ShellTestCase { private PipTransitionState.PipTransitionStateChangedListener mStateChangedListener; private Parcelable mEmptyParcelable; + @Mock + private Handler mMainHandler; + @Before public void setUp() { - mPipTransitionState = new PipTransitionState(); + mPipTransitionState = new PipTransitionState(mMainHandler); mPipTransitionState.setState(PipTransitionState.UNDEFINED); mEmptyParcelable = new Bundle(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 5cf9be4e7aca..884cb6ec9f74 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -59,6 +59,7 @@ import android.view.SurfaceControl; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.window.flags.Flags; import com.android.wm.shell.ShellTaskOrganizer; @@ -66,8 +67,8 @@ import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayInsetsController; import com.android.wm.shell.common.TaskStackListenerImpl; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; @@ -75,11 +76,13 @@ import com.android.wm.shell.sysui.ShellSharedConstants; import com.android.wm.shell.util.GroupedRecentTaskInfo; import com.android.wm.shell.util.SplitBounds; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.quality.Strictness; import java.util.ArrayList; import java.util.Arrays; @@ -88,7 +91,9 @@ import java.util.Optional; import java.util.function.Consumer; /** - * Tests for {@link RecentTasksController}. + * Tests for {@link RecentTasksController} + * + * Usage: atest WMShellUnitTests:RecentTasksControllerTest */ @RunWith(AndroidJUnit4.class) @SmallTest @@ -118,9 +123,15 @@ public class RecentTasksControllerTest extends ShellTestCase { private ShellInit mShellInit; private ShellController mShellController; private TestShellExecutor mMainExecutor; + private static StaticMockitoSession sMockitoSession; @Before public void setUp() { + sMockitoSession = mockitoSession().initMocks(this).strictness(Strictness.LENIENT) + .mockStatic(DesktopModeStatus.class).startMocking(); + ExtendedMockito.doReturn(true) + .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); + mMainExecutor = new TestShellExecutor(); when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class)); mShellInit = spy(new ShellInit(mMainExecutor)); @@ -136,6 +147,11 @@ public class RecentTasksControllerTest extends ShellTestCase { mShellInit.init(); } + @After + public void tearDown() { + sMockitoSession.finishMocking(); + } + @Test public void instantiateController_addInitCallback() { verify(mShellInit, times(1)).addInitCallback(any(), isA(RecentTasksController.class)); @@ -275,10 +291,6 @@ public class RecentTasksControllerTest extends ShellTestCase { @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() { - StaticMockitoSession mockitoSession = mockitoSession().mockStatic( - DesktopModeStatus.class).startMocking(); - when(DesktopModeStatus.isEnabled()).thenReturn(true); - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); @@ -309,16 +321,10 @@ public class RecentTasksControllerTest extends ShellTestCase { // Check single entries assertEquals(t2, singleGroup1.getTaskInfo1()); assertEquals(t4, singleGroup2.getTaskInfo1()); - - mockitoSession.finishMocking(); } @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() { - StaticMockitoSession mockitoSession = mockitoSession().mockStatic( - DesktopModeStatus.class).startMocking(); - when(DesktopModeStatus.isEnabled()).thenReturn(true); - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); @@ -357,15 +363,12 @@ public class RecentTasksControllerTest extends ShellTestCase { // Check single entry assertEquals(t4, singleGroup.getTaskInfo1()); - - mockitoSession.finishMocking(); } @Test public void testGetRecentTasks_hasActiveDesktopTasks_proto2Disabled_doNotGroupFreeformTasks() { - StaticMockitoSession mockitoSession = mockitoSession().mockStatic( - DesktopModeStatus.class).startMocking(); - when(DesktopModeStatus.isEnabled()).thenReturn(false); + ExtendedMockito.doReturn(false) + .when(() -> DesktopModeStatus.canEnterDesktopMode(any())); ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); @@ -390,16 +393,10 @@ public class RecentTasksControllerTest extends ShellTestCase { assertEquals(t2, recentTasks.get(1).getTaskInfo1()); assertEquals(t3, recentTasks.get(2).getTaskInfo1()); assertEquals(t4, recentTasks.get(3).getTaskInfo1()); - - mockitoSession.finishMocking(); } @Test public void testGetRecentTasks_proto2Enabled_ignoresMinimizedFreeformTasks() { - StaticMockitoSession mockitoSession = mockitoSession().mockStatic( - DesktopModeStatus.class).startMocking(); - when(DesktopModeStatus.isEnabled()).thenReturn(true); - ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1); ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2); ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3); @@ -435,8 +432,6 @@ public class RecentTasksControllerTest extends ShellTestCase { // Check single entries assertEquals(t2, singleGroup1.getTaskInfo1()); assertEquals(t4, singleGroup2.getTaskInfo1()); - - mockitoSession.finishMocking(); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index befc702b01aa..34b2eebb15a1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -39,10 +39,13 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.ActivityManager; @@ -63,6 +66,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; +import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; @@ -105,6 +109,8 @@ public class SplitTransitionTests extends ShellTestCase { @Mock private ShellExecutor mMainExecutor; @Mock private LaunchAdjacentController mLaunchAdjacentController; @Mock private DefaultMixedHandler mMixedHandler; + @Mock private SplitScreen.SplitInvocationListener mInvocationListener; + private final TestShellExecutor mTestShellExecutor = new TestShellExecutor(); private SplitLayout mSplitLayout; private MainStage mMainStage; private SideStage mSideStage; @@ -147,6 +153,7 @@ public class SplitTransitionTests extends ShellTestCase { .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build(); doReturn(mock(SplitDecorManager.class)).when(mMainStage).getSplitDecorManager(); doReturn(mock(SplitDecorManager.class)).when(mSideStage).getSplitDecorManager(); + mStageCoordinator.registerSplitAnimationListener(mInvocationListener, mTestShellExecutor); } @Test @@ -452,6 +459,15 @@ public class SplitTransitionTests extends ShellTestCase { mMainStage.activate(new WindowContainerTransaction(), true /* includingTopTask */); } + @Test + @UiThreadTest + public void testSplitInvocationCallback() { + enterSplit(); + mTestShellExecutor.flushAll(); + verify(mInvocationListener, times(1)) + .onSplitAnimationInvoked(eq(true)); + } + private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) { for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index d7c383523a6f..d18fec2f24ad 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -16,10 +16,7 @@ package com.android.wm.shell.splitscreen; -import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN; import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED; -import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -31,8 +28,9 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; @@ -45,6 +43,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.res.Configuration; import android.graphics.Rect; @@ -54,7 +53,6 @@ import android.os.Looper; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.window.RemoteTransition; -import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.annotation.UiThreadTest; @@ -343,14 +341,14 @@ public class StageCoordinatorTests extends ShellTestCase { @Test public void testAddActivityOptions_addsBackgroundActivitiesFlags() { - Bundle options = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN, + Bundle bundle = mStageCoordinator.resolveStartStage(STAGE_TYPE_MAIN, SPLIT_POSITION_UNDEFINED, null /* options */, null /* wct */); + ActivityOptions options = ActivityOptions.fromBundle(bundle); - assertEquals(options.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, WindowContainerToken.class), - mMainStage.mRootTaskInfo.token); - assertTrue(options.getBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)); - assertTrue(options.getBoolean( - KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION)); + assertThat(options.getLaunchRootTask()).isEqualTo(mMainStage.mRootTaskInfo.token); + assertThat(options.getPendingIntentBackgroundActivityStartMode()) + .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + assertThat(options.isPendingIntentBackgroundActivityLaunchAllowedByPermission()).isTrue(); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 7d19f3cbf659..282495de0fc1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -60,8 +60,8 @@ import com.android.wm.shell.common.DisplayInsetsController import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue -import com.android.wm.shell.desktopmode.DesktopModeStatus import com.android.wm.shell.desktopmode.DesktopTasksController +import com.android.wm.shell.shared.DesktopModeStatus import com.android.wm.shell.sysui.KeyguardChangeListener import com.android.wm.shell.sysui.ShellCommandHandler import com.android.wm.shell.sysui.ShellController @@ -121,6 +121,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockShellController: ShellController @Mock private lateinit var mockShellExecutor: ShellExecutor @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + @Mock private lateinit var mockResizeHandleSizeRepository: ResizeHandleSizeRepository @Mock private lateinit var mockShellCommandHandler: ShellCommandHandler @Mock private lateinit var mockWindowManager: IWindowManager @@ -156,7 +157,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockInputMonitorFactory, transactionFactory, mockRootTaskDisplayAreaOrganizer, - windowDecorByTaskIdSpy + windowDecorByTaskIdSpy, + mockResizeHandleSizeRepository ) whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) @@ -197,7 +199,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockMainHandler, mockMainChoreographer, mockSyncQueue, - mockRootTaskDisplayAreaOrganizer + mockRootTaskDisplayAreaOrganizer, + mockResizeHandleSizeRepository ) verify(decoration).close() } @@ -221,7 +224,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockMainHandler, mockMainChoreographer, mockSyncQueue, - mockRootTaskDisplayAreaOrganizer + mockRootTaskDisplayAreaOrganizer, + mockResizeHandleSizeRepository ) task.setWindowingMode(WINDOWING_MODE_FREEFORM) @@ -236,7 +240,8 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockMainHandler, mockMainChoreographer, mockSyncQueue, - mockRootTaskDisplayAreaOrganizer + mockRootTaskDisplayAreaOrganizer, + mockResizeHandleSizeRepository ) } @@ -296,7 +301,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskChanging(task) verify(mockDesktopModeWindowDecorFactory, never()) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any()) } @Test @@ -309,7 +314,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory, never()) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any()) } @Test @@ -406,7 +411,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory, never()) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any()) } finally { mockitoSession.finishMocking() } @@ -430,7 +435,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any()) } finally { mockitoSession.finishMocking() } @@ -453,7 +458,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { onTaskOpening(task) verify(mockDesktopModeWindowDecorFactory) - .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + .create(any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any()) } finally { mockitoSession.finishMocking() } @@ -497,7 +502,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { val decoration = mock(DesktopModeWindowDecoration::class.java) whenever( mockDesktopModeWindowDecorFactory.create( - any(), any(), any(), eq(task), any(), any(), any(), any(), any()) + any(), any(), any(), eq(task), any(), any(), any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.isFocused).thenReturn(task.isFocused) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index 608f74b95280..e737861873d2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -19,6 +19,7 @@ package com.android.wm.shell.windowdecor; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND; import static com.google.common.truth.Truth.assertThat; @@ -39,6 +40,9 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.os.Handler; import android.os.SystemProperties; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.view.Choreographer; @@ -51,6 +55,7 @@ import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; @@ -61,6 +66,7 @@ import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -83,6 +89,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY = "persist.wm.debug.desktop_use_rounded_corners"; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + @Mock private DisplayController mMockDisplayController; @Mock @@ -107,6 +115,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory; @Mock private TypedArray mMockRoundedCornersRadiusArray; + @Mock + private ResizeHandleSizeRepository mMockResizeHandleSizeRepository; private final Configuration mConfiguration = new Configuration(); @@ -175,6 +185,48 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test + @EnableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY) + public void updateRelayoutParams_appHeader_usesTaskDensity() { + final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources() + .getConfiguration().densityDpi; + final int customTaskDensity = systemDensity + 300; + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); + taskInfo.configuration.densityDpi = customTaskDensity; + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); + + assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(customTaskDensity); + } + + @Test + @DisableFlags(Flags.FLAG_ENABLE_APP_HEADER_WITH_TASK_DENSITY) + public void updateRelayoutParams_appHeader_usesSystemDensity() { + final int systemDensity = mTestableContext.getOrCreateTestableResources().getResources() + .getConfiguration().densityDpi; + final int customTaskDensity = systemDensity + 300; + final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); + taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + taskInfo.configuration.densityDpi = customTaskDensity; + final RelayoutParams relayoutParams = new RelayoutParams(); + + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); + + assertThat(relayoutParams.mWindowDecorConfig.densityDpi).isEqualTo(systemDensity); + } + + @Test public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -294,10 +346,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { private DesktopModeWindowDecoration createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo) { return new DesktopModeWindowDecoration(mContext, mMockDisplayController, - mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mConfiguration, + mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl, mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer, - SurfaceControl.Builder::new, mMockTransactionSupplier, - WindowContainerTransaction::new, SurfaceControl::new, + mMockResizeHandleSizeRepository, SurfaceControl.Builder::new, + mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, mMockSurfaceControlViewHostFactory); } 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..62fb1c441118 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 @@ -27,10 +27,7 @@ import static com.google.common.truth.Truth.assertThat; import android.annotation.NonNull; import android.graphics.Point; import android.graphics.Region; -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.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.util.Size; @@ -56,6 +53,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( @@ -72,7 +71,7 @@ public class DragResizeWindowGeometryTests { TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); /** * Check that both groups of objects satisfy equals/hashcode within each group, and that each @@ -90,13 +89,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 +122,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(); } /** @@ -144,11 +144,14 @@ public class DragResizeWindowGeometryTests { * capture all eligible input regardless of source (touch or cursor). */ @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) public void testRegionUnion_edgeDragResizeEnabled_containsLargeCorners() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE); 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); } @@ -158,26 +161,28 @@ public class DragResizeWindowGeometryTests { * size. */ @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) public void testRegionUnion_edgeDragResizeDisabled_containsFineCorners() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE); 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); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) public void testCalculateControlType_edgeDragResizeEnabled_edges() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE); // The input source (touch or cursor) shouldn't impact the edge resize size. validateCtrlTypeForEdges(/* isTouch= */ false); validateCtrlTypeForEdges(/* isTouch= */ true); } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) public void testCalculateControlType_edgeDragResizeDisabled_edges() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE); // Edge resizing is not supported when the flag is disabled. validateCtrlTypeForEdges(/* isTouch= */ false); validateCtrlTypeForEdges(/* isTouch= */ false); @@ -195,8 +200,8 @@ public class DragResizeWindowGeometryTests { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) public void testCalculateControlType_edgeDragResizeEnabled_corners() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE); final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2); final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2); @@ -218,8 +223,8 @@ public class DragResizeWindowGeometryTests { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) public void testCalculateControlType_edgeDragResizeDisabled_corners() { + mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE); final TestPoints fineTestPoints = new TestPoints(TASK_SIZE, FINE_CORNER_SIZE / 2); final TestPoints largeCornerTestPoints = new TestPoints(TASK_SIZE, LARGE_CORNER_SIZE / 2); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt new file mode 100644 index 000000000000..a9fddc623b96 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryParameterizedTests.kt @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.windowdecor + +import android.content.Context +import android.platform.test.flag.junit.SetFlagsRule +import androidx.test.core.app.ApplicationProvider +import androidx.test.filters.SmallTest +import com.android.window.flags.Flags +import java.util.function.Consumer +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.junit.runners.Parameterized.Parameter +import org.junit.runners.Parameterized.Parameters +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +/** + * Tests for {@link ResizeHandleSizeRepository}. + * + * Build/Install/Run: atest WMShellUnitTests:ResizeHandleSizeRepositoryParameterizedTests + */ +@SmallTest +@RunWith(Parameterized::class) +class ResizeHandleSizeRepositoryParameterizedTests { + private val resources = ApplicationProvider.getApplicationContext<Context>().resources + private val resizeHandleSizeRepository = ResizeHandleSizeRepository() + @Mock private lateinit var mockSizeChangeFunctionOne: Consumer<ResizeHandleSizeRepository> + @Mock private lateinit var mockSizeChangeFunctionTwo: Consumer<ResizeHandleSizeRepository> + + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + @Parameter(0) lateinit var name: String + // The current ResizeHandleSizeRepository API under test. + + @Parameter(1) lateinit var operation: (ResizeHandleSizeRepository) -> Unit + + @Before + fun setOverrideBeforeResetResizeHandle() { + MockitoAnnotations.initMocks(this) + if (name != "reset") return + val originalEdgeHandle = + resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources) + resizeHandleSizeRepository.setResizeEdgeHandlePixels(originalEdgeHandle + 2) + } + + companion object { + @Parameters(name = "{index}: {0}") + @JvmStatic + fun data(): Iterable<Array<Any>> { + return listOf( + arrayOf( + "reset", + { sizeRepository: ResizeHandleSizeRepository -> + sizeRepository.resetResizeEdgeHandlePixels() + } + ), + arrayOf( + "set", + { sizeRepository: ResizeHandleSizeRepository -> + sizeRepository.setResizeEdgeHandlePixels(99) + } + ) + ) + } + } + + // ================= + // Validate that listeners are notified correctly for reset resize handle API. + // ================= + + @Test + fun testUpdateResizeHandleSize_flagDisabled() { + setFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + registerSizeChangeFunctions() + operation.invoke(resizeHandleSizeRepository) + // Nothing is notified since flag is disabled. + verify(mockSizeChangeFunctionOne, never()).accept(any()) + verify(mockSizeChangeFunctionTwo, never()).accept(any()) + } + + @Test + fun testUpdateResizeHandleSize_flagEnabled_noListeners() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + operation.invoke(resizeHandleSizeRepository) + // Nothing is notified since nothing was registered. + verify(mockSizeChangeFunctionOne, never()).accept(any()) + verify(mockSizeChangeFunctionTwo, never()).accept(any()) + } + + @Test + fun testUpdateResizeHandleSize_flagEnabled_listenersNotified() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + registerSizeChangeFunctions() + operation.invoke(resizeHandleSizeRepository) + // Functions notified when reset. + verify(mockSizeChangeFunctionOne).accept(any()) + verify(mockSizeChangeFunctionTwo).accept(any()) + } + + @Test + fun testUpdateResizeHandleSize_flagEnabled_listenerFails() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + registerSizeChangeFunctions() + operation.invoke(resizeHandleSizeRepository) + // Functions notified when reset. + verify(mockSizeChangeFunctionOne).accept(any()) + verify(mockSizeChangeFunctionTwo).accept(any()) + } + + @Test + fun testUpdateResizeHandleSize_flagEnabled_ignoreSecondListener() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + registerSizeChangeFunctions() + val extraConsumerMock = mock(Consumer::class.java) as Consumer<ResizeHandleSizeRepository> + resizeHandleSizeRepository.registerSizeChangeFunction(extraConsumerMock) + // First listener succeeds, second one that fails is ignored. + operation.invoke(resizeHandleSizeRepository) + // Functions notified when reset. + verify(mockSizeChangeFunctionOne).accept(any()) + verify(mockSizeChangeFunctionTwo).accept(any()) + verify(extraConsumerMock).accept(any()) + } + + private fun registerSizeChangeFunctions() { + resizeHandleSizeRepository.registerSizeChangeFunction(mockSizeChangeFunctionOne) + resizeHandleSizeRepository.registerSizeChangeFunction(mockSizeChangeFunctionTwo) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt new file mode 100644 index 000000000000..59bbc72a733b --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/ResizeHandleSizeRepositoryTests.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.windowdecor + +import android.content.Context +import android.platform.test.flag.junit.SetFlagsRule +import android.testing.AndroidTestingRunner +import androidx.test.core.app.ApplicationProvider +import androidx.test.filters.SmallTest +import com.android.window.flags.Flags +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Tests for {@link ResizeHandleSizeRepository}. Validate that get/reset/set work correctly. + * + * Build/Install/Run: atest WMShellUnitTests:ResizeHandleSizeRepositoryTests + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class ResizeHandleSizeRepositoryTests { + private val resources = ApplicationProvider.getApplicationContext<Context>().resources + private val resizeHandleSizeRepository = ResizeHandleSizeRepository() + + @JvmField @Rule val setFlagsRule = SetFlagsRule() + + @Test + fun testOverrideResizeEdgeHandlePixels_flagEnabled_resetSucceeds() { + setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + // Reset does nothing when no override is set. + val originalEdgeHandle = + resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources) + resizeHandleSizeRepository.resetResizeEdgeHandlePixels() + assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)) + .isEqualTo(originalEdgeHandle) + + // Now try to set the value; reset should succeed. + resizeHandleSizeRepository.setResizeEdgeHandlePixels(originalEdgeHandle + 2) + resizeHandleSizeRepository.resetResizeEdgeHandlePixels() + assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)) + .isEqualTo(originalEdgeHandle) + } + + @Test + fun testOverrideResizeEdgeHandlePixels_flagDisabled_resetFails() { + setFlagsRule.disableFlags(Flags.FLAG_ENABLE_WINDOWING_EDGE_DRAG_RESIZE) + // Reset does nothing when no override is set. + val originalEdgeHandle = + resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources) + resizeHandleSizeRepository.resetResizeEdgeHandlePixels() + assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)) + .isEqualTo(originalEdgeHandle) + + // Now try to set the value; reset should do nothing. + val newEdgeHandle = originalEdgeHandle + 2 + resizeHandleSizeRepository.setResizeEdgeHandlePixels(newEdgeHandle) + resizeHandleSizeRepository.resetResizeEdgeHandlePixels() + assertThat(resizeHandleSizeRepository.getResizeEdgeHandlePixels(resources)) + .isEqualTo(originalEdgeHandle) + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 4eb44d747486..48310810e8c9 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -49,7 +49,6 @@ import static org.mockito.quality.Strictness.LENIENT; import android.app.ActivityManager; import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Point; @@ -75,7 +74,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; -import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.tests.R; import org.junit.Before; @@ -134,7 +133,6 @@ public class WindowDecorationTests extends ShellTestCase { private SurfaceControl.Transaction mMockSurfaceControlFinishT; private SurfaceControl.Transaction mMockSurfaceControlAddWindowT; private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams(); - private Configuration mWindowConfiguration = new Configuration(); private int mCaptionMenuWidthId; @Before @@ -303,7 +301,6 @@ public class WindowDecorationTests extends ShellTestCase { taskInfo.isFocused = true; // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mWindowConfiguration.densityDpi = taskInfo.configuration.densityDpi; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); @@ -314,14 +311,16 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockWindowContainerTransaction, never()) .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt()); + final SurfaceControl.Transaction t2 = mock(SurfaceControl.Transaction.class); + mMockSurfaceControlTransactions.add(t2); taskInfo.isVisible = false; windowDecor.relayout(taskInfo); - final InOrder releaseOrder = inOrder(t, mMockSurfaceControlViewHost); + final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost); releaseOrder.verify(mMockSurfaceControlViewHost).release(); - releaseOrder.verify(t).remove(captionContainerSurface); - releaseOrder.verify(t).remove(decorContainerSurface); - releaseOrder.verify(t).apply(); + releaseOrder.verify(t2).remove(captionContainerSurface); + releaseOrder.verify(t2).remove(decorContainerSurface); + releaseOrder.verify(t2).apply(); // Expect to remove two insets sources, the caption insets and the mandatory gesture insets. verify(mMockWindowContainerTransaction, Mockito.times(2)) .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt()); @@ -836,7 +835,7 @@ public class WindowDecorationTests extends ShellTestCase { private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) { return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer, - taskInfo, mMockTaskSurface, mWindowConfiguration, + taskInfo, mMockTaskSurface, new MockObjectSupplier<>(mMockSurfaceControlBuilders, () -> createMockSurfaceControlBuilder(mock(SurfaceControl.class))), new MockObjectSupplier<>(mMockSurfaceControlTransactions, @@ -877,16 +876,15 @@ public class WindowDecorationTests extends ShellTestCase { TestWindowDecoration(Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, - Configuration windowConfiguration, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory) { super(context, displayController, taskOrganizer, taskInfo, taskSurface, - windowConfiguration, surfaceControlBuilderSupplier, - surfaceControlTransactionSupplier, windowContainerTransactionSupplier, - surfaceControlSupplier, surfaceControlViewHostFactory); + surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, + windowContainerTransactionSupplier, surfaceControlSupplier, + surfaceControlViewHostFactory); } @Override 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/Android.bp b/libs/hwui/Android.bp index 753a69960b4c..7c1c5b4e7e5f 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -336,6 +336,7 @@ cc_defaults { "jni/android_graphics_animation_NativeInterpolatorFactory.cpp", "jni/android_graphics_animation_RenderNodeAnimator.cpp", "jni/android_graphics_Canvas.cpp", + "jni/android_graphics_Color.cpp", "jni/android_graphics_ColorSpace.cpp", "jni/android_graphics_drawable_AnimatedVectorDrawable.cpp", "jni/android_graphics_drawable_VectorDrawable.cpp", 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/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp index fd9915a54bb5..70a9ef04d6f3 100644 --- a/libs/hwui/apex/LayoutlibLoader.cpp +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -46,6 +46,7 @@ namespace android { extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_CanvasProperty(JNIEnv* env); +extern int register_android_graphics_Color(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); extern int register_android_graphics_ColorSpace(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); @@ -87,6 +88,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.Camera", REG_JNI(register_android_graphics_Camera)}, {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)}, {"android.graphics.CanvasProperty", REG_JNI(register_android_graphics_CanvasProperty)}, + {"android.graphics.Color", REG_JNI(register_android_graphics_Color)}, {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)}, {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)}, {"android.graphics.CreateJavaOutputStreamAdaptor", diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index fb0cdb034575..6ace3967ecf3 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -49,6 +49,7 @@ namespace android { extern int register_android_graphics_Canvas(JNIEnv* env); extern int register_android_graphics_CanvasProperty(JNIEnv* env); extern int register_android_graphics_ColorFilter(JNIEnv* env); +extern int register_android_graphics_Color(JNIEnv* env); extern int register_android_graphics_ColorSpace(JNIEnv* env); extern int register_android_graphics_DrawFilter(JNIEnv* env); extern int register_android_graphics_FontFamily(JNIEnv* env); @@ -98,6 +99,7 @@ extern int register_android_graphics_HardwareBufferRenderer(JNIEnv* env); static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Canvas), + REG_JNI(register_android_graphics_Color), // This needs to be before register_android_graphics_Graphics, or the latter // will not be able to find the jmethodID for ColorSpace.get(). REG_JNI(register_android_graphics_ColorSpace), diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp index 3d0a53440bfb..785aef312072 100644 --- a/libs/hwui/jni/BitmapFactory.cpp +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -688,8 +688,8 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteA static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, jobject padding, jobject bitmapFactoryOptions, jlong inBitmapHandle, jlong colorSpaceHandle) { -#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC - return nullObjectReturn("Not supported on Windows"); +#ifdef _WIN32 // LayoutLib for Windows does not support F_DUPFD_CLOEXEC + return nullObjectReturn("Not supported on Windows"); #else NPE_CHECK_RETURN_ZERO(env, fileDescriptor); diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp index a952be020855..2a057e7a4cdc 100644 --- a/libs/hwui/jni/Shader.cpp +++ b/libs/hwui/jni/Shader.cpp @@ -36,25 +36,6 @@ static const uint32_t sGradientShaderFlags = SkGradientShader::kInterpolateColor return 0; \ } -static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue, jfloatArray hsvArray) -{ - SkScalar hsv[3]; - SkRGBToHSV(red, green, blue, hsv); - - AutoJavaFloatArray autoHSV(env, hsvArray, 3); - float* values = autoHSV.ptr(); - for (int i = 0; i < 3; i++) { - values[i] = SkScalarToFloat(hsv[i]); - } -} - -static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray) -{ - AutoJavaFloatArray autoHSV(env, hsvArray, 3); - SkScalar* hsv = autoHSV.ptr(); - return static_cast<jint>(SkHSVToColor(alpha, hsv)); -} - /////////////////////////////////////////////////////////////////////////////////////////////// static void Shader_safeUnref(SkShader* shader) { @@ -409,11 +390,6 @@ static void RuntimeShader_updateShader(JNIEnv* env, jobject, jlong shaderBuilder /////////////////////////////////////////////////////////////////////////////////////////////// -static const JNINativeMethod gColorMethods[] = { - { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV }, - { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor } -}; - static const JNINativeMethod gShaderMethods[] = { { "nativeGetFinalizer", "()J", (void*)Shader_getNativeFinalizer }, }; @@ -456,8 +432,6 @@ static const JNINativeMethod gRuntimeShaderMethods[] = { int register_android_graphics_Shader(JNIEnv* env) { - android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods, - NELEM(gColorMethods)); android::RegisterMethodsOrDie(env, "android/graphics/Shader", gShaderMethods, NELEM(gShaderMethods)); android::RegisterMethodsOrDie(env, "android/graphics/BitmapShader", gBitmapShaderMethods, diff --git a/libs/hwui/jni/android_graphics_Color.cpp b/libs/hwui/jni/android_graphics_Color.cpp new file mode 100644 index 000000000000..c22b8b926373 --- /dev/null +++ b/libs/hwui/jni/android_graphics_Color.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GraphicsJNI.h" + +#include "SkColor.h" + +using namespace android; + +static void Color_RGBToHSV(JNIEnv* env, jobject, jint red, jint green, jint blue, + jfloatArray hsvArray) +{ + SkScalar hsv[3]; + SkRGBToHSV(red, green, blue, hsv); + + AutoJavaFloatArray autoHSV(env, hsvArray, 3); + float* values = autoHSV.ptr(); + for (int i = 0; i < 3; i++) { + values[i] = SkScalarToFloat(hsv[i]); + } +} + +static jint Color_HSVToColor(JNIEnv* env, jobject, jint alpha, jfloatArray hsvArray) +{ + AutoJavaFloatArray autoHSV(env, hsvArray, 3); + SkScalar* hsv = autoHSV.ptr(); + return static_cast<jint>(SkHSVToColor(alpha, hsv)); +} + +static const JNINativeMethod gColorMethods[] = { + { "nativeRGBToHSV", "(III[F)V", (void*)Color_RGBToHSV }, + { "nativeHSVToColor", "(I[F)I", (void*)Color_HSVToColor } +}; + +namespace android { + +int register_android_graphics_Color(JNIEnv* env) { + return android::RegisterMethodsOrDie(env, "android/graphics/Color", gColorMethods, + NELEM(gColorMethods)); +} + +}; // namespace android diff --git a/libs/hwui/jni/android_graphics_ColorSpace.cpp b/libs/hwui/jni/android_graphics_ColorSpace.cpp index 63d3f83febd6..d06206be90d7 100644 --- a/libs/hwui/jni/android_graphics_ColorSpace.cpp +++ b/libs/hwui/jni/android_graphics_ColorSpace.cpp @@ -148,7 +148,7 @@ static const JNINativeMethod gColorSpaceRgbMethods[] = { namespace android { int register_android_graphics_ColorSpace(JNIEnv* env) { - return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb", + return android::RegisterMethodsOrDie(env, "android/graphics/ColorSpace$Rgb$Native", gColorSpaceRgbMethods, NELEM(gColorSpaceRgbMethods)); } diff --git a/libs/hwui/jni/android_graphics_Matrix.cpp b/libs/hwui/jni/android_graphics_Matrix.cpp index c0d791a88908..eedc069ed01b 100644 --- a/libs/hwui/jni/android_graphics_Matrix.cpp +++ b/libs/hwui/jni/android_graphics_Matrix.cpp @@ -326,9 +326,6 @@ public: }; static const JNINativeMethod methods[] = { - {"nGetNativeFinalizer", "()J", (void*) SkMatrixGlue::getNativeFinalizer}, - {"nCreate","(J)J", (void*) SkMatrixGlue::create}, - // ------- @FastNative below here --------------- {"nMapPoints","(J[FI[FIIZ)V", (void*) SkMatrixGlue::mapPoints}, {"nMapRect","(JLandroid/graphics/RectF;Landroid/graphics/RectF;)Z", @@ -388,9 +385,6 @@ static jmethodID sCtor; int register_android_graphics_Matrix(JNIEnv* env) { // Methods only used on Ravenwood (for now). See the javadoc on Matrix$ExtraNativesx // for why we need it. - // - // We don't need it on non-ravenwood, but we don't (yet) have a way to detect ravenwood - // environment, so we just always run it. RegisterMethodsOrDie(env, "android/graphics/Matrix$ExtraNatives", extra_methods, NELEM(extra_methods)); diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 6a465442c2b4..f1ee3256dbee 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::LogicalDisplayId::INVALID for displayId here to avoid removing the callback * if a TouchSpotController with the same display is removed. */ - mContext.addAnimationCallback(-1, func); + mContext.addAnimationCallback(ui::LogicalDisplayId::INVALID, 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 f9dc5fac7e21..cca1b07c3118 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -24,7 +24,6 @@ #include <SkColor.h> #include <android-base/stringprintf.h> #include <android-base/thread_annotations.h> -#include <com_android_input_flags.h> #include <ftl/enum.h> #include <mutex> @@ -35,14 +34,10 @@ #define INDENT2 " " #define INDENT3 " " -namespace input_flags = com::android::input::flags; - namespace android { namespace { -static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer(); - const ui::Transform kIdentityTransform; } // namespace @@ -68,27 +63,24 @@ void PointerController::DisplayInfoListener::onPointerControllerDestroyed() { std::shared_ptr<PointerController> PointerController::create( const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController, bool enabled, ControllerType type) { + SpriteController& spriteController, ControllerType type) { // using 'new' to access non-public constructor std::shared_ptr<PointerController> controller; switch (type) { case ControllerType::MOUSE: controller = std::shared_ptr<PointerController>( - new MousePointerController(policy, looper, spriteController, enabled)); + new MousePointerController(policy, looper, spriteController)); break; case ControllerType::TOUCH: controller = std::shared_ptr<PointerController>( - new TouchPointerController(policy, looper, spriteController, enabled)); + new TouchPointerController(policy, looper, spriteController)); break; case ControllerType::STYLUS: controller = std::shared_ptr<PointerController>( - new StylusPointerController(policy, looper, spriteController, enabled)); + new StylusPointerController(policy, looper, spriteController)); break; - case ControllerType::LEGACY: default: - controller = std::shared_ptr<PointerController>( - new PointerController(policy, looper, spriteController, enabled)); - break; + LOG_ALWAYS_FATAL("Invalid ControllerType: %d", static_cast<int>(type)); } /* @@ -108,10 +100,9 @@ std::shared_ptr<PointerController> PointerController::create( } PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, SpriteController& spriteController, - bool enabled) + const sp<Looper>& looper, SpriteController& spriteController) : PointerController( - policy, looper, spriteController, enabled, + policy, looper, spriteController, [](const sp<android::gui::WindowInfosListener>& listener) { auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{}, std::vector<android::gui::DisplayInfo>{}); @@ -125,11 +116,9 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, SpriteController& spriteController, - bool enabled, const WindowListenerRegisterConsumer& registerListener, WindowListenerUnregisterConsumer unregisterListener) - : mEnabled(enabled), - mContext(policy, looper, spriteController, *this), + : mContext(policy, looper, spriteController, *this), mCursorController(mContext), mDisplayInfoListener(sp<DisplayInfoListener>::make(this)), mUnregisterWindowInfosListener(std::move(unregisterListener)) { @@ -142,7 +131,6 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& PointerController::~PointerController() { mDisplayInfoListener->onPointerControllerDestroyed(); mUnregisterWindowInfosListener(mDisplayInfoListener); - mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, FloatPoint{0, 0}); } std::mutex& PointerController::getLock() const { @@ -150,15 +138,11 @@ std::mutex& PointerController::getLock() const { } std::optional<FloatRect> PointerController::getBounds() const { - if (!mEnabled) return {}; - return mCursorController.getBounds(); } void PointerController::move(float deltaX, float deltaY) { - if (!mEnabled) return; - - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; { std::scoped_lock lock(getLock()); @@ -169,9 +153,7 @@ void PointerController::move(float deltaX, float deltaY) { } void PointerController::setPosition(float x, float y) { - if (!mEnabled) return; - - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; { std::scoped_lock lock(getLock()); @@ -182,11 +164,7 @@ void PointerController::setPosition(float x, float y) { } FloatPoint PointerController::getPosition() const { - if (!mEnabled) { - return FloatPoint{0, 0}; - } - - const int32_t displayId = mCursorController.getDisplayId(); + const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); const auto p = mCursorController.getPosition(); { std::scoped_lock lock(getLock()); @@ -195,29 +173,21 @@ FloatPoint PointerController::getPosition() const { } } -int32_t PointerController::getDisplayId() const { - if (!mEnabled) return ADISPLAY_ID_NONE; - +ui::LogicalDisplayId PointerController::getDisplayId() const { return mCursorController.getDisplayId(); } void PointerController::fade(Transition transition) { - if (!mEnabled) return; - std::scoped_lock lock(getLock()); mCursorController.fade(transition); } void PointerController::unfade(Transition transition) { - if (!mEnabled) return; - std::scoped_lock lock(getLock()); mCursorController.unfade(transition); } void PointerController::setPresentation(Presentation presentation) { - if (!mEnabled) return; - std::scoped_lock lock(getLock()); if (mLocked.presentation == presentation) { @@ -226,33 +196,13 @@ void PointerController::setPresentation(Presentation presentation) { mLocked.presentation = presentation; - if (ENABLE_POINTER_CHOREOGRAPHER) { - // When pointer choreographer is enabled, the presentation mode is only set once when the - // PointerController is constructed, before the display viewport is provided. - // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer - // is permanently enabled. The presentation can be set in the constructor. - mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER); - return; - } - - if (!mCursorController.isViewportValid()) { - return; - } - - if (presentation == Presentation::POINTER || presentation == Presentation::STYLUS_HOVER) { - // For now, we support stylus hover using the mouse cursor implementation. - // TODO: Add proper support for stylus hover icons. - mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER); - - mCursorController.getAdditionalMouseResources(); - clearSpotsLocked(); - } + // The presentation mode is only set once when the PointerController is constructed, + // before the display viewport is provided. + mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER); } void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId) { - if (!mEnabled) return; - + BitSet32 spotIdBits, ui::LogicalDisplayId displayId) { std::scoped_lock lock(getLock()); std::array<PointerCoords, MAX_POINTERS> outSpotCoords{}; const ui::Transform& transform = getTransformForDisplayLocked(displayId); @@ -272,12 +222,13 @@ void PointerController::setSpots(const PointerCoords* spotCoords, const uint32_t if (it == mLocked.spotControllers.end()) { mLocked.spotControllers.try_emplace(displayId, displayId, mContext); } - mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits); + bool skipScreenshot = mLocked.displaysToSkipScreenshot.find(displayId) != + mLocked.displaysToSkipScreenshot.end(); + mLocked.spotControllers.at(displayId).setSpots(outSpotCoords.data(), spotIdToIndex, spotIdBits, + skipScreenshot); } void PointerController::clearSpots() { - if (!mEnabled) return; - std::scoped_lock lock(getLock()); clearSpotsLocked(); } @@ -310,12 +261,6 @@ void PointerController::reloadPointerResources() { } void PointerController::setDisplayViewport(const DisplayViewport& viewport) { - struct PointerDisplayChangeArgs { - int32_t displayId; - FloatPoint cursorPosition; - }; - std::optional<PointerDisplayChangeArgs> pointerDisplayChanged; - { // acquire lock std::scoped_lock lock(getLock()); @@ -327,44 +272,42 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources); if (viewport.displayId != mLocked.pointerDisplayId) { mLocked.pointerDisplayId = viewport.displayId; - pointerDisplayChanged = {viewport.displayId, mCursorController.getPosition()}; } } // release lock - - if (pointerDisplayChanged) { - // Notify the policy without holding the pointer controller lock. - mContext.getPolicy()->onPointerDisplayIdChanged(pointerDisplayChanged->displayId, - pointerDisplayChanged->cursorPosition); - } } void PointerController::updatePointerIcon(PointerIconStyle iconId) { - if (!mEnabled) return; - std::scoped_lock lock(getLock()); mCursorController.updatePointerIcon(iconId); } void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { - if (!mEnabled) return; - std::scoped_lock lock(getLock()); mCursorController.setCustomPointerIcon(icon); } +void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) { + std::scoped_lock lock(getLock()); + if (skip) { + mLocked.displaysToSkipScreenshot.insert(displayId); + } else { + mLocked.displaysToSkipScreenshot.erase(displayId); + } +} + void PointerController::doInactivityTimeout() { fade(Transition::GRADUAL); } 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 @@ -383,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; @@ -392,15 +336,12 @@ const ui::Transform& PointerController::getTransformForDisplayLocked(int display } std::string PointerController::dump() { - if (!mEnabled) { - return INDENT "PointerController: DISABLED due to ongoing PointerChoreographer refactor\n"; - } - std::string dump = INDENT "PointerController:\n"; 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); @@ -416,8 +357,8 @@ std::string PointerController::dump() { MousePointerController::MousePointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController, bool enabled) - : PointerController(policy, looper, spriteController, enabled) { + SpriteController& spriteController) + : PointerController(policy, looper, spriteController) { PointerController::setPresentation(Presentation::POINTER); } @@ -429,8 +370,8 @@ MousePointerController::~MousePointerController() { TouchPointerController::TouchPointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController, bool enabled) - : PointerController(policy, looper, spriteController, enabled) { + SpriteController& spriteController) + : PointerController(policy, looper, spriteController) { PointerController::setPresentation(Presentation::SPOT); } @@ -442,8 +383,8 @@ TouchPointerController::~TouchPointerController() { StylusPointerController::StylusPointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController, bool enabled) - : PointerController(policy, looper, spriteController, enabled) { + SpriteController& spriteController) + : PointerController(policy, looper, spriteController) { PointerController::setPresentation(Presentation::STYLUS_HOVER); } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 6ee5707622ca..c6430f7f36ff 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -47,8 +47,7 @@ class PointerController : public PointerControllerInterface { public: static std::shared_ptr<PointerController> create( const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController, bool enabled, - ControllerType type = ControllerType::LEGACY); + SpriteController& spriteController, ControllerType type); ~PointerController() override; @@ -56,17 +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(ui::LogicalDisplayId displayId, bool skip) override; virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout); void doInactivityTimeout(); @@ -86,12 +86,12 @@ protected: // Constructor used to test WindowInfosListener registration. PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController, bool enabled, + SpriteController& spriteController, const WindowListenerRegisterConsumer& registerListener, WindowListenerUnregisterConsumer unregisterListener); PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, - SpriteController& spriteController, bool enabled); + SpriteController& spriteController); private: friend PointerControllerContext::LooperCallback; @@ -103,18 +103,17 @@ private: // we use the DisplayInfoListener's lock in PointerController. std::mutex& getLock() const; - const bool mEnabled; - PointerControllerContext mContext; MouseCursorController mCursorController; struct Locked { Presentation presentation; - int32_t pointerDisplayId = ADISPLAY_ID_NONE; + ui::LogicalDisplayId pointerDisplayId = ui::LogicalDisplayId::INVALID; std::vector<gui::DisplayInfo> mDisplayInfos; - std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; + std::unordered_map<ui::LogicalDisplayId, TouchSpotController> spotControllers; + std::unordered_set<ui::LogicalDisplayId> displaysToSkipScreenshot; } mLocked GUARDED_BY(getLock()); class DisplayInfoListener : public gui::WindowInfosListener { @@ -133,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()); }; @@ -142,15 +142,14 @@ class MousePointerController : public PointerController { public: /** A version of PointerController that controls one mouse pointer. */ MousePointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, SpriteController& spriteController, - bool enabled); + const sp<Looper>& looper, SpriteController& spriteController); ~MousePointerController() override; 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 { @@ -162,8 +161,7 @@ class TouchPointerController : public PointerController { public: /** A version of PointerController that controls touch spots. */ TouchPointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, SpriteController& spriteController, - bool enabled); + const sp<Looper>& looper, SpriteController& spriteController); ~TouchPointerController() override; @@ -179,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 { @@ -208,15 +206,14 @@ class StylusPointerController : public PointerController { public: /** A version of PointerController that controls one stylus pointer. */ StylusPointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, SpriteController& spriteController, - bool enabled); + const sp<Looper>& looper, SpriteController& spriteController); ~StylusPointerController() override; 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 98c3988e7df4..d42214883d3a 100644 --- a/libs/input/PointerControllerContext.h +++ b/libs/input/PointerControllerContext.h @@ -72,16 +72,16 @@ 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; - virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0; }; /* @@ -103,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); @@ -113,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: @@ -137,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(); @@ -149,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 a63453d655e2..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 { @@ -129,7 +129,7 @@ void SpriteController::doUpdateSprites() { update.state.surfaceVisible = false; update.state.surfaceControl = obtainSurface(update.state.surfaceWidth, update.state.surfaceHeight, - update.state.displayId); + update.state.displayId, update.state.skipScreenshot); if (update.state.surfaceControl != NULL) { update.surfaceChanged = surfaceChanged = true; } @@ -209,7 +209,7 @@ void SpriteController::doUpdateSprites() { (update.state.dirty & (DIRTY_ALPHA | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER | DIRTY_VISIBILITY | DIRTY_HOTSPOT | DIRTY_DISPLAY_ID | DIRTY_ICON_STYLE | - DIRTY_DRAW_DROP_SHADOW))))) { + DIRTY_DRAW_DROP_SHADOW | DIRTY_SKIP_SCREENSHOT))))) { needApplyTransaction = true; if (wantSurfaceVisibleAndDrawn @@ -260,6 +260,14 @@ void SpriteController::doUpdateSprites() { t.setLayer(update.state.surfaceControl, surfaceLayer); } + if (wantSurfaceVisibleAndDrawn && + (becomingVisible || (update.state.dirty & DIRTY_SKIP_SCREENSHOT))) { + int32_t flags = + update.state.skipScreenshot ? ISurfaceComposerClient::eSkipScreenshot : 0; + t.setFlags(update.state.surfaceControl, flags, + ISurfaceComposerClient::eSkipScreenshot); + } + if (becomingVisible) { t.show(update.state.surfaceControl); @@ -333,19 +341,22 @@ void SpriteController::ensureSurfaceComposerClient() { } sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height, - int32_t displayId) { + 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; + if (hideOnMirrored) { + createFlags |= ISurfaceComposerClient::eSkipScreenshot; + } const sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(String8("Sprite"), width, height, - PIXEL_FORMAT_RGBA_8888, - ISurfaceComposerClient::eHidden | - ISurfaceComposerClient::eCursorWindow, + PIXEL_FORMAT_RGBA_8888, createFlags, parent ? parent->getHandle() : nullptr); if (surfaceControl == nullptr || !surfaceControl->isValid()) { ALOGE("Error creating sprite surface."); @@ -465,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) { @@ -474,6 +485,15 @@ void SpriteController::SpriteImpl::setDisplayId(int32_t displayId) { } } +void SpriteController::SpriteImpl::setSkipScreenshot(bool skip) { + AutoMutex _l(mController.mLock); + + if (mLocked.state.skipScreenshot != skip) { + mLocked.state.skipScreenshot = skip; + invalidateLocked(DIRTY_SKIP_SCREENSHOT); + } +} + void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) { bool wasDirty = mLocked.state.dirty; mLocked.state.dirty |= dirty; diff --git a/libs/input/SpriteController.h b/libs/input/SpriteController.h index 35776e9961b3..e147c567ae2d 100644 --- a/libs/input/SpriteController.h +++ b/libs/input/SpriteController.h @@ -95,7 +95,11 @@ 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. */ + virtual void setSkipScreenshot(bool skip) = 0; }; /* @@ -111,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; @@ -152,6 +156,7 @@ private: DIRTY_DISPLAY_ID = 1 << 7, DIRTY_ICON_STYLE = 1 << 8, DIRTY_DRAW_DROP_SHADOW = 1 << 9, + DIRTY_SKIP_SCREENSHOT = 1 << 10, }; /* Describes the state of a sprite. @@ -160,28 +165,23 @@ private: * on the sprites for a long time. * Note that the SpriteIcon holds a reference to a shared (and immutable) bitmap. */ struct SpriteState { - inline SpriteState() : - dirty(0), visible(false), - positionX(0), positionY(0), layer(0), alpha(1.0f), displayId(ADISPLAY_ID_DEFAULT), - surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) { - } - - uint32_t dirty; + uint32_t dirty{0}; SpriteIcon icon; - bool visible; - float positionX; - float positionY; - int32_t layer; - float alpha; + bool visible{false}; + float positionX{0}; + float positionY{0}; + int32_t layer{0}; + float alpha{1.0f}; SpriteTransformationMatrix transformationMatrix; - int32_t displayId; + ui::LogicalDisplayId displayId{ui::LogicalDisplayId::DEFAULT}; sp<SurfaceControl> surfaceControl; - int32_t surfaceWidth; - int32_t surfaceHeight; - bool surfaceDrawn; - bool surfaceVisible; + int32_t surfaceWidth{0}; + int32_t surfaceHeight{0}; + bool surfaceDrawn{false}; + bool surfaceVisible{false}; + bool skipScreenshot{false}; inline bool wantSurfaceVisible() const { return visible && alpha > 0.0f && icon.isValid(); @@ -208,7 +208,8 @@ 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 { return mLocked.state; @@ -272,7 +273,8 @@ 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); }; } // namespace android diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp index 99952aa14904..7462481f8779 100644 --- a/libs/input/TouchSpotController.cpp +++ b/libs/input/TouchSpotController.cpp @@ -40,12 +40,13 @@ namespace android { // --- Spot --- void TouchSpotController::Spot::updateSprite(const SpriteIcon* icon, float newX, float newY, - int32_t displayId) { + ui::LogicalDisplayId displayId, bool skipScreenshot) { sprite->setLayer(Sprite::BASE_LAYER_SPOT + id); sprite->setAlpha(alpha); sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale)); sprite->setPosition(newX, newY); sprite->setDisplayId(displayId); + sprite->setSkipScreenshot(skipScreenshot); x = newX; y = newY; @@ -68,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); } @@ -84,7 +86,7 @@ TouchSpotController::~TouchSpotController() { } void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits) { + BitSet32 spotIdBits, bool skipScreenshot) { #if DEBUG_SPOT_UPDATES ALOGD("setSpots: idBits=%08x", spotIdBits.value); for (BitSet32 idBits(spotIdBits); !idBits.isEmpty();) { @@ -93,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 @@ -116,7 +118,7 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32 spot = createAndAddSpotLocked(id, mLocked.displaySpots); } - spot->updateSprite(&icon, x, y, mDisplayId); + spot->updateSprite(&icon, x, y, mDisplayId, skipScreenshot); } for (Spot* spot : mLocked.displaySpots) { @@ -273,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 5bbc75d9570b..ac37fa430249 100644 --- a/libs/input/TouchSpotController.h +++ b/libs/input/TouchSpotController.h @@ -29,10 +29,10 @@ 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); + BitSet32 spotIdBits, bool skipScreenshot); void clearSpots(); void reloadSpotResources(); @@ -59,14 +59,15 @@ 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; 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 a1bb5b3f1cc4..2dcb1f1d1650 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include <com_android_input_flags.h> #include <flag_macros.h> #include <gmock/gmock.h> #include <gtest/gtest.h> @@ -30,8 +29,6 @@ namespace android { -namespace input_flags = com::android::input::flags; - enum TestCursorType { CURSOR_TYPE_DEFAULT = 0, CURSOR_TYPE_HOVER, @@ -55,20 +52,19 @@ 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; - virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override; bool allResourcesAreLoaded(); bool noResourcesAreLoaded(); - std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; } private: void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType); @@ -76,16 +72,15 @@ private: bool pointerIconLoaded{false}; bool pointerResourcesLoaded{false}; bool additionalMouseResourcesLoaded{false}; - std::optional<int32_t /*displayId*/> latestPointerDisplayId; }; -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); @@ -94,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; @@ -146,12 +141,6 @@ void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* ic icon->hotSpotY = hotSpot.second; } -void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId, - const FloatPoint& /*position*/ -) { - latestPointerDisplayId = displayId; -} - class TestPointerController : public PointerController { public: TestPointerController(sp<android::gui::WindowInfosListener>& registeredListener, @@ -159,7 +148,6 @@ public: SpriteController& spriteController) : PointerController( policy, looper, spriteController, - /*enabled=*/true, [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) -> std::vector<gui::DisplayInfo> { // Register listener @@ -178,7 +166,7 @@ protected: PointerControllerTest(); ~PointerControllerTest(); - void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT); + void ensureDisplayViewportIsSet(ui::LogicalDisplayId displayId = ui::LogicalDisplayId::DEFAULT); sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; @@ -217,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; @@ -267,8 +255,7 @@ TEST_F(PointerControllerTest, useStylusTypeForStylusHover) { mPointerController->reloadPointerResources(); } -TEST_F_WITH_FLAGS(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) { +TEST_F(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources) { // Setting the presentation mode before a display viewport is set will not load any resources. mPointerController->setPresentation(PointerController::Presentation::POINTER); ASSERT_TRUE(mPolicy->noResourcesAreLoaded()); @@ -278,26 +265,7 @@ TEST_F_WITH_FLAGS(PointerControllerTest, setPresentationBeforeDisplayViewportDoe ASSERT_TRUE(mPolicy->allResourcesAreLoaded()); } -TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIcon, - REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(input_flags, - enable_pointer_choreographer))) { - ensureDisplayViewportIsSet(); - mPointerController->setPresentation(PointerController::Presentation::POINTER); - mPointerController->unfade(PointerController::Transition::IMMEDIATE); - - int32_t type = CURSOR_TYPE_ADDITIONAL; - std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type); - EXPECT_CALL(*mPointerSprite, setVisible(true)); - EXPECT_CALL(*mPointerSprite, setAlpha(1.0f)); - EXPECT_CALL(*mPointerSprite, - setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)), - Field(&SpriteIcon::hotSpotX, hotspot.first), - Field(&SpriteIcon::hotSpotY, hotspot.second)))); - mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type)); -} - -TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIconWithChoreographer, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) { +TEST_F(PointerControllerTest, updatePointerIconWithChoreographer) { // When PointerChoreographer is enabled, the presentation mode is set before the viewport. mPointerController->setPresentation(PointerController::Presentation::POINTER); ensureDisplayViewportIsSet(); @@ -348,28 +316,43 @@ TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) { ensureDisplayViewportIsSet(); } -TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) { - EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId()) - << "A pointer display change does not occur when PointerController is created."; - - ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT); - - const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId(); - ASSERT_TRUE(lastReportedPointerDisplayId) - << "The policy is notified of a pointer display change when the viewport is first set."; - EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId) - << "Incorrect pointer display notified."; - - ensureDisplayViewportIsSet(42); - - EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId()) - << "The policy is notified when the pointer display changes."; - - // Release the PointerController. - mPointerController = nullptr; +TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { + ensureDisplayViewportIsSet(); - EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId()) - << "The pointer display changes to invalid when PointerController is destroyed."; + PointerCoords testSpotCoords; + testSpotCoords.clear(); + testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_X, 1); + testSpotCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, 1); + BitSet32 testIdBits; + testIdBits.markBit(0); + std::array<uint32_t, MAX_POINTER_ID + 1> testIdToIndex; + + sp<MockSprite> testSpotSprite(new NiceMock<MockSprite>); + + // By default sprite is not marked secure + EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testSpotSprite)); + EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false)); + + // Update spots to sync state with sprite + mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, + ui::LogicalDisplayId::DEFAULT); + testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); + + // Marking the display to skip screenshot should update sprite as well + mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, true); + EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true)); + + // Update spots to sync state with sprite + mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, + ui::LogicalDisplayId::DEFAULT); + testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); + + // Reset flag and verify again + mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false); + EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false)); + mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits, + ui::LogicalDisplayId::DEFAULT); + testing::Mock::VerifyAndClearExpectations(testSpotSprite.get()); } class PointerControllerWindowInfoListenerTest : public Test {}; diff --git a/libs/input/tests/mocks/MockSprite.h b/libs/input/tests/mocks/MockSprite.h index 013b79c3a3bf..21628fb9f72c 100644 --- a/libs/input/tests/mocks/MockSprite.h +++ b/libs/input/tests/mocks/MockSprite.h @@ -33,7 +33,8 @@ 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)); }; } // namespace android 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/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl index e2dddad274e1..85bc8efe2750 100644 --- a/media/java/android/media/IMediaRouter2.aidl +++ b/media/java/android/media/IMediaRouter2.aidl @@ -36,6 +36,5 @@ oneway interface IMediaRouter2 { * Call MediaRouterService#requestCreateSessionWithRouter2 to pass the result. */ void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession, - in MediaRoute2Info route, in UserHandle transferInitiatorUserHandle, - in String transferInitiatorPackageName); + in MediaRoute2Info route); } diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index 63cb94516739..efbf8da6d17c 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -64,8 +64,7 @@ interface IMediaRouterService { void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId, in RoutingSessionInfo oldSession, in MediaRoute2Info route, - in @nullable Bundle sessionHints, in UserHandle transferInitiatorUserHandle, - in String transferInitiatorPackageName); + in @nullable Bundle sessionHints); void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route); void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route); void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId, diff --git a/media/java/android/media/LoudnessCodecDispatcher.java b/media/java/android/media/LoudnessCodecDispatcher.java index fa08658a214f..bdd3c7396a5a 100644 --- a/media/java/android/media/LoudnessCodecDispatcher.java +++ b/media/java/android/media/LoudnessCodecDispatcher.java @@ -16,6 +16,9 @@ package android.media; +import static android.media.MediaFormat.KEY_AAC_DRC_ALBUM_MODE; +import static android.media.MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR; +import static android.media.MediaFormat.KEY_AAC_DRC_BOOST_FACTOR; import static android.media.MediaFormat.KEY_AAC_DRC_EFFECT_TYPE; import static android.media.MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION; import static android.media.MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL; @@ -142,6 +145,18 @@ public class LoudnessCodecDispatcher implements CallbackUtil.DispatcherStub { filteredBundle.putInt(KEY_AAC_DRC_EFFECT_TYPE, bundle.getInt(KEY_AAC_DRC_EFFECT_TYPE)); } + if (bundle.containsKey(KEY_AAC_DRC_BOOST_FACTOR)) { + filteredBundle.putInt(KEY_AAC_DRC_BOOST_FACTOR, + bundle.getInt(KEY_AAC_DRC_BOOST_FACTOR)); + } + if (bundle.containsKey(KEY_AAC_DRC_ATTENUATION_FACTOR)) { + filteredBundle.putInt(KEY_AAC_DRC_ATTENUATION_FACTOR, + bundle.getInt(KEY_AAC_DRC_ATTENUATION_FACTOR)); + } + if (bundle.containsKey(KEY_AAC_DRC_ALBUM_MODE)) { + filteredBundle.putInt(KEY_AAC_DRC_ALBUM_MODE, + bundle.getInt(KEY_AAC_DRC_ALBUM_MODE)); + } return filteredBundle; } diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index 554fe5efac3d..679e8a1b95e6 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -50,6 +50,7 @@ import android.util.Log; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import com.android.media.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -984,45 +985,13 @@ public final class MediaRouter2 { @SystemApi @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) { - mImpl.transfer( - controller.getRoutingSessionInfo(), - route, - Process.myUserHandle(), - mContext.getPackageName()); - } - - /** - * Transfers the media of a routing controller to the given route. - * - * <p>This will be no-op for non-system media routers. - * - * @param controller a routing controller controlling media routing. - * @param route the route you want to transfer the media to. - * @param transferInitiatorUserHandle the user handle of the app that initiated the transfer - * request. - * @param transferInitiatorPackageName the package name of the app that initiated the transfer. - * This value is used with the user handle to populate {@link - * RoutingController#wasTransferInitiatedBySelf()}. - * @hide - */ - public void transfer( - @NonNull RoutingController controller, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { - mImpl.transfer( - controller.getRoutingSessionInfo(), - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + mImpl.transfer(controller.getRoutingSessionInfo(), route); } void requestCreateController( @NonNull RoutingController controller, @NonNull MediaRoute2Info route, - long managerRequestId, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + long managerRequestId) { final int requestId = mNextRequestId.getAndIncrement(); @@ -1051,9 +1020,7 @@ public final class MediaRouter2 { managerRequestId, controller.getRoutingSessionInfo(), route, - controllerHints, - transferInitiatorUserHandle, - transferInitiatorPackageName); + controllerHints); } catch (RemoteException ex) { Log.e(TAG, "createControllerForTransfer: " + "Failed to request for creating a controller.", ex); @@ -1395,11 +1362,7 @@ public final class MediaRouter2 { } void onRequestCreateControllerByManagerOnHandler( - RoutingSessionInfo oldSession, - MediaRoute2Info route, - long managerRequestId, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + RoutingSessionInfo oldSession, MediaRoute2Info route, long managerRequestId) { Log.i( TAG, TextUtils.formatSimple( @@ -1416,8 +1379,7 @@ public final class MediaRouter2 { if (controller == null) { return; } - requestCreateController(controller, route, managerRequestId, transferInitiatorUserHandle, - transferInitiatorPackageName); + requestCreateController(controller, route, managerRequestId); } private List<MediaRoute2Info> getSortedRoutes( @@ -1913,13 +1875,7 @@ public final class MediaRouter2 { */ @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) public boolean wasTransferInitiatedBySelf() { - RoutingSessionInfo sessionInfo = getRoutingSessionInfo(); - - UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle(); - String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName(); - - return Objects.equals(Process.myUserHandle(), transferInitiatorUserHandle) - && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName); + return mImpl.wasTransferredBySelf(getRoutingSessionInfo()); } /** @@ -2064,24 +2020,47 @@ public final class MediaRouter2 { } /** - * Transfers to a given route for the remote session. The given route must be included in - * {@link RoutingSessionInfo#getTransferableRoutes()}. + * Attempts a transfer to a {@link RoutingSessionInfo#getTransferableRoutes() transferable + * route}. * + * <p>Transferring to a transferable route does not require the app to transfer the playback + * state from one route to the other. The route provider completely manages the transfer. An + * example of provider-managed transfers are the switches between the system's routes, like + * the built-in speakers and a BT headset. + * + * @return True if the transfer is handled by this controller, or false if a new controller + * should be created instead. * @see RoutingSessionInfo#getSelectedRoutes() * @see RoutingSessionInfo#getTransferableRoutes() * @see ControllerCallback#onControllerUpdated */ - void transferToRoute(@NonNull MediaRoute2Info route) { + boolean tryTransferWithinProvider(@NonNull MediaRoute2Info route) { Objects.requireNonNull(route, "route must not be null"); synchronized (mControllerLock) { if (isReleased()) { - Log.w(TAG, "transferToRoute: Called on released controller. Ignoring."); - return; + Log.w( + TAG, + "tryTransferWithinProvider: Called on released controller. Ignoring."); + return true; } - if (!mSessionInfo.getTransferableRoutes().contains(route.getId())) { - Log.w(TAG, "Ignoring transferring to a non-transferable route=" + route); - return; + // If this call is trying to transfer to a selected system route, we let them + // through as a provider driven transfer in order to update the transfer reason and + // initiator data. + boolean isSystemRouteReselection = + Flags.enableBuiltInSpeakerRouteSuitabilityStatuses() + && mSessionInfo.isSystemSession() + && route.isSystemRoute() + && mSessionInfo.getSelectedRoutes().contains(route.getId()); + if (!isSystemRouteReselection + && !mSessionInfo.getTransferableRoutes().contains(route.getId())) { + Log.i( + TAG, + "Transferring to a non-transferable route=" + + route + + " session= " + + mSessionInfo.getId()); + return false; } } @@ -2096,6 +2075,7 @@ public final class MediaRouter2 { Log.e(TAG, "Unable to transfer to route for session.", ex); } } + return true; } /** @@ -2434,20 +2414,14 @@ public final class MediaRouter2 { @Override public void requestCreateSessionByManager( - long managerRequestId, - RoutingSessionInfo oldSession, - MediaRoute2Info route, - UserHandle transferInitiatorUserHandle, - String transferInitiatorPackageName) { + long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) { mHandler.sendMessage( obtainMessage( MediaRouter2::onRequestCreateControllerByManagerOnHandler, MediaRouter2.this, oldSession, route, - managerRequestId, - transferInitiatorUserHandle, - transferInitiatorPackageName)); + managerRequestId)); } } @@ -2490,11 +2464,7 @@ public final class MediaRouter2 { void stop(); - void transfer( - @NonNull RoutingSessionInfo sessionInfo, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName); + void transfer(@NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route); List<RoutingController> getControllers(); @@ -2515,6 +2485,11 @@ public final class MediaRouter2 { boolean shouldNotifyStop, RoutingController controller); + /** + * Returns the value of {@link RoutingController#wasTransferInitiatedBySelf()} for the app + * associated with this router. + */ + boolean wasTransferredBySelf(RoutingSessionInfo sessionInfo); } /** @@ -2715,7 +2690,7 @@ public final class MediaRouter2 { List<RoutingSessionInfo> sessionInfos = getRoutingSessions(); RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1); - transfer(targetSession, route, mClientUser, mContext.getPackageName()); + transfer(targetSession, route); } @Override @@ -2738,24 +2713,15 @@ public final class MediaRouter2 { * * @param sessionInfo The {@link RoutingSessionInfo routing session} to transfer. * @param route The {@link MediaRoute2Info route} to transfer to. - * @param transferInitiatorUserHandle The user handle of the app that initiated the - * transfer. - * @param transferInitiatorPackageName The package name if of the app that initiated the - * transfer. * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info, UserHandle, String) * @see #requestCreateSession(RoutingSessionInfo, MediaRoute2Info) */ @Override @SuppressWarnings("AndroidFrameworkRequiresPermission") public void transfer( - @NonNull RoutingSessionInfo sessionInfo, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) { Objects.requireNonNull(sessionInfo, "sessionInfo must not be null"); Objects.requireNonNull(route, "route must not be null"); - Objects.requireNonNull(transferInitiatorUserHandle); - Objects.requireNonNull(transferInitiatorPackageName); Log.v( TAG, @@ -2772,15 +2738,19 @@ public final class MediaRouter2 { return; } - if (sessionInfo.getTransferableRoutes().contains(route.getId())) { - transferToRoute( - sessionInfo, - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + // If this call is trying to transfer to a selected system route, we let them + // through as a provider driven transfer in order to update the transfer reason and + // initiator data. + boolean isSystemRouteReselection = + Flags.enableBuiltInSpeakerRouteSuitabilityStatuses() + && sessionInfo.isSystemSession() + && route.isSystemRoute() + && sessionInfo.getSelectedRoutes().contains(route.getId()); + if (sessionInfo.getTransferableRoutes().contains(route.getId()) + || isSystemRouteReselection) { + transferToRoute(sessionInfo, route, mClientUser, mClientPackageName); } else { - requestCreateSession(sessionInfo, route, transferInitiatorUserHandle, - transferInitiatorPackageName); + requestCreateSession(sessionInfo, route, mClientUser, mClientPackageName); } } @@ -3035,6 +3005,14 @@ public final class MediaRouter2 { releaseSession(controller.getRoutingSessionInfo()); } + @Override + public boolean wasTransferredBySelf(RoutingSessionInfo sessionInfo) { + UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle(); + String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName(); + return Objects.equals(mClientUser, transferInitiatorUserHandle) + && Objects.equals(mClientPackageName, transferInitiatorPackageName); + } + /** * Retrieves the system session info for the given package. * @@ -3587,20 +3565,9 @@ public final class MediaRouter2 { } RoutingController controller = getCurrentController(); - if (controller - .getRoutingSessionInfo() - .getTransferableRoutes() - .contains(route.getId())) { - controller.transferToRoute(route); - return; + if (!controller.tryTransferWithinProvider(route)) { + requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE); } - - requestCreateController( - controller, - route, - MANAGER_REQUEST_ID_NONE, - Process.myUserHandle(), - mContext.getPackageName()); } @Override @@ -3617,10 +3584,7 @@ public final class MediaRouter2 { */ @Override public void transfer( - @NonNull RoutingSessionInfo sessionInfo, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) { // Do nothing. } @@ -3739,6 +3703,14 @@ public final class MediaRouter2 { } } + @Override + public boolean wasTransferredBySelf(RoutingSessionInfo sessionInfo) { + UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle(); + String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName(); + return Objects.equals(Process.myUserHandle(), transferInitiatorUserHandle) + && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName); + } + @GuardedBy("mLock") private void registerRouterStubIfNeededLocked() throws RemoteException { if (mStub == null) { diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index 223b432c12af..40592915a3a8 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -109,7 +109,7 @@ public final class MediaProjection { try { final Callback c = Objects.requireNonNull(callback); if (handler == null) { - handler = new Handler(); + handler = new Handler(mContext.getMainLooper()); } mCallbacks.put(c, new CallbackRecord(c, handler)); } catch (NullPointerException e) { diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java index b43ff63f3fcc..70462effaa54 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -252,18 +252,14 @@ public final class MediaController { return 0; } - /** - * Get the current playback info for this session. - * - * @return The current playback info or null. - */ - public @Nullable PlaybackInfo getPlaybackInfo() { + /** Returns the current playback info for this session. */ + @NonNull + public PlaybackInfo getPlaybackInfo() { try { return mSessionBinder.getVolumeAttributes(); - } catch (RemoteException e) { - Log.wtf(TAG, "Error calling getAudioInfo.", e); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); } - return null; } /** @@ -618,12 +614,11 @@ public final class MediaController { } /** - * Override to handle changes to the audio info. + * Signals a change in the session's {@link PlaybackInfo PlaybackInfo}. * - * @param info The current audio info for this session. + * @param playbackInfo The latest known state of the session's playback info. */ - public void onAudioInfoChanged(PlaybackInfo info) { - } + public void onAudioInfoChanged(@NonNull PlaybackInfo playbackInfo) {} } /** @@ -1186,7 +1181,7 @@ public final class MediaController { } @Override - public void onVolumeInfoChanged(PlaybackInfo info) { + public void onVolumeInfoChanged(@NonNull PlaybackInfo info) { MediaController controller = mController.get(); if (controller != null) { controller.postMessage(MSG_UPDATE_VOLUME, info, null); diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp index 83056b267365..02d72ad7d693 100644 --- a/native/android/performance_hint.cpp +++ b/native/android/performance_hint.cpp @@ -60,7 +60,7 @@ public: APerformanceHintSession* createSession(const int32_t* threadIds, size_t size, int64_t initialTargetWorkDurationNanos, - hal::SessionTag tag = hal::SessionTag::OTHER); + hal::SessionTag tag = hal::SessionTag::APP); int64_t getPreferredRateNanos() const; private: diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp index 58f56b873246..78a53578f5ca 100644 --- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp +++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp @@ -16,6 +16,7 @@ #define LOG_TAG "PerformanceHintNativeTest" +#include <aidl/android/hardware/power/ChannelConfig.h> #include <aidl/android/hardware/power/SessionConfig.h> #include <aidl/android/hardware/power/SessionTag.h> #include <aidl/android/hardware/power/WorkDuration.h> @@ -54,6 +55,9 @@ public: MOCK_METHOD(ScopedAStatus, getHintSessionThreadIds, (const std::shared_ptr<IHintSession>& hintSession, ::std::vector<int32_t>* tids), (override)); + MOCK_METHOD(ScopedAStatus, getSessionChannel, + (const ::ndk::SpAIBinder& in_token, hal::ChannelConfig* _aidl_return), (override)); + MOCK_METHOD(ScopedAStatus, closeSessionChannel, (), (override)); MOCK_METHOD(SpAIBinder, asBinder, (), (override)); MOCK_METHOD(bool, isRemote, (), (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/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl index 7cd7e7ab49a9..7150b54cf7f1 100644 --- a/nfc/java/android/nfc/INfcAdapter.aidl +++ b/nfc/java/android/nfc/INfcAdapter.aidl @@ -91,7 +91,7 @@ interface INfcAdapter boolean enableReaderOption(boolean enable); boolean isObserveModeSupported(); boolean isObserveModeEnabled(); - boolean setObserveMode(boolean enabled); + boolean setObserveMode(boolean enabled, String pkg); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)") boolean setWlcEnabled(boolean enable); diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java index e43d10422729..698df28129be 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 { @@ -1264,8 +1268,12 @@ public final class NfcAdapter { @FlaggedApi(Flags.FLAG_NFC_OBSERVE_MODE) public boolean setObserveModeEnabled(boolean enabled) { + if (mContext == null) { + throw new UnsupportedOperationException("You need a context on NfcAdapter to use the " + + " observe mode APIs"); + } try { - return sService.setObserveMode(enabled); + return sService.setObserveMode(enabled, mContext.getPackageName()); } catch (RemoteException e) { attemptDeadServiceRecovery(e); return false; diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java index de9eada18104..2fe2ce39813b 100644 --- a/nfc/java/android/nfc/cardemulation/CardEmulation.java +++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java @@ -1212,16 +1212,16 @@ public final class CardEmulation { * * @param service The ComponentName of the service * @param status true to enable, false to disable + * @param userId the user handle of the user whose information is being requested. * @return set service for the category and true if service is already set return false. * * @hide */ - public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status) { + public boolean setServiceEnabledForCategoryOther(ComponentName service, boolean status, + int userId) { if (service == null) { throw new NullPointerException("activity or service or category is null"); } - int userId = mContext.getUser().getIdentifier(); - try { return sService.setServiceEnabledForCategoryOther(userId, service, status); } catch (RemoteException e) { diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java index f674b06ad33d..c3c74a6fd265 100644 --- a/nfc/java/android/nfc/cardemulation/HostApduService.java +++ b/nfc/java/android/nfc/cardemulation/HostApduService.java @@ -20,6 +20,7 @@ import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; @@ -404,6 +405,7 @@ public abstract class HostApduService extends Service { * * @param frame A description of the polling frame. */ + @SuppressLint("OnNameExpected") @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public void processPollingFrames(@NonNull List<PollingFrame> frame) { } diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java index b52faba79ed7..4c76fb02f7d8 100644 --- a/nfc/java/android/nfc/cardemulation/PollingFrame.java +++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java @@ -44,8 +44,15 @@ public final class PollingFrame implements Parcelable{ /** * @hide */ - @IntDef(prefix = { "POLLING_LOOP_TYPE_"}, value = { POLLING_LOOP_TYPE_A, POLLING_LOOP_TYPE_B, - POLLING_LOOP_TYPE_F, POLLING_LOOP_TYPE_OFF, POLLING_LOOP_TYPE_ON }) + @IntDef(prefix = { "POLLING_LOOP_TYPE_"}, + value = { + POLLING_LOOP_TYPE_A, + POLLING_LOOP_TYPE_B, + POLLING_LOOP_TYPE_F, + POLLING_LOOP_TYPE_OFF, + POLLING_LOOP_TYPE_ON, + POLLING_LOOP_TYPE_UNKNOWN + }) @Retention(RetentionPolicy.SOURCE) @FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP) public @interface PollingFrameType {} 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/CarrierDefaultApp/res/values-ca/strings.xml b/packages/CarrierDefaultApp/res/values-ca/strings.xml index ec032d5281df..6e6a862c2c91 100644 --- a/packages/CarrierDefaultApp/res/values-ca/strings.xml +++ b/packages/CarrierDefaultApp/res/values-ca/strings.xml @@ -16,7 +16,7 @@ <string name="ssl_error_continue" msgid="1138548463994095584">"Continua igualment mitjançant el navegador"</string> <string name="performance_boost_notification_channel" msgid="3475440855635538592">"Optimització de rendiment"</string> <string name="performance_boost_notification_title" msgid="3126203390685781861">"Opcions 5G del teu operador"</string> - <string name="performance_boost_notification_detail" msgid="216569851036236346">"Visita el lloc web de %s per veure les opcions relacionades amb la teva experiència a l\'aplicació"</string> + <string name="performance_boost_notification_detail" msgid="216569851036236346">"Visita el lloc web de %s per veure les opcions relacionades amb la teva experiència de l\'aplicació"</string> <string name="performance_boost_notification_button_not_now" msgid="6459755324243683785">"Ara no"</string> <string name="performance_boost_notification_button_manage" msgid="4976836444046497973">"Gestiona"</string> <string name="slice_purchase_app_label" msgid="7170191659233241166">"Compra una optimització de rendiment."</string> diff --git a/packages/CarrierDefaultApp/res/values-fa/strings.xml b/packages/CarrierDefaultApp/res/values-fa/strings.xml index abf47fbfe2cd..d9afe873b444 100644 --- a/packages/CarrierDefaultApp/res/values-fa/strings.xml +++ b/packages/CarrierDefaultApp/res/values-fa/strings.xml @@ -17,7 +17,7 @@ <string name="performance_boost_notification_channel" msgid="3475440855635538592">"تقویتکننده عملکرد"</string> <string name="performance_boost_notification_title" msgid="3126203390685781861">"گزینههای نسل پنجم شرکت مخابراتی شما"</string> <string name="performance_boost_notification_detail" msgid="216569851036236346">"برای دیدن گزینههای مرتبط با تجربه برنامه، به وبسایت %s مراجعه کنید"</string> - <string name="performance_boost_notification_button_not_now" msgid="6459755324243683785">"اکنون نه"</string> + <string name="performance_boost_notification_button_not_now" msgid="6459755324243683785">"حالا نه"</string> <string name="performance_boost_notification_button_manage" msgid="4976836444046497973">"مدیریت"</string> <string name="slice_purchase_app_label" msgid="7170191659233241166">"تقویتکننده عملکرد خریداری کنید."</string> </resources> 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/CompanionDeviceManager/res/values-ky/strings.xml b/packages/CompanionDeviceManager/res/values-ky/strings.xml index 1b72477f5e33..23a2e7faafda 100644 --- a/packages/CompanionDeviceManager/res/values-ky/strings.xml +++ b/packages/CompanionDeviceManager/res/values-ky/strings.xml @@ -59,7 +59,7 @@ <string name="permission_microphone" msgid="2152206421428732949">"Микрофон"</string> <string name="permission_call_logs" msgid="5546761417694586041">"Чалуулар тизмеси"</string> <string name="permission_nearby_devices" msgid="7530973297737123481">"Жакын жердеги түзмөктөр"</string> - <string name="permission_media_routing_control" msgid="5498639511586715253">"Медиа чыгарылышын өзгөртүү"</string> + <string name="permission_media_routing_control" msgid="5498639511586715253">"Медианы чыгаруучу булакты өзгөртүү"</string> <string name="permission_storage" msgid="6831099350839392343">"Сүрөттөр жана медиафайлдар"</string> <string name="permission_notifications" msgid="4099418516590632909">"Билдирмелер"</string> <string name="permission_app_streaming" msgid="6009695219091526422">"Колдонмолор"</string> diff --git a/packages/CompanionDeviceManager/res/values-mk/strings.xml b/packages/CompanionDeviceManager/res/values-mk/strings.xml index 2d7c2d3962e5..6a162c0c89a2 100644 --- a/packages/CompanionDeviceManager/res/values-mk/strings.xml +++ b/packages/CompanionDeviceManager/res/values-mk/strings.xml @@ -59,7 +59,7 @@ <string name="permission_microphone" msgid="2152206421428732949">"Микрофон"</string> <string name="permission_call_logs" msgid="5546761417694586041">"Евиденција на повици"</string> <string name="permission_nearby_devices" msgid="7530973297737123481">"Уреди во близина"</string> - <string name="permission_media_routing_control" msgid="5498639511586715253">"Промена на излезот за аудио"</string> + <string name="permission_media_routing_control" msgid="5498639511586715253">"Менување излез за аудио/видео"</string> <string name="permission_storage" msgid="6831099350839392343">"Аудиовизуелни содржини"</string> <string name="permission_notifications" msgid="4099418516590632909">"Известувања"</string> <string name="permission_app_streaming" msgid="6009695219091526422">"Апликации"</string> diff --git a/packages/CompanionDeviceManager/res/values-tr/strings.xml b/packages/CompanionDeviceManager/res/values-tr/strings.xml index 5852657a01b5..815d256abea2 100644 --- a/packages/CompanionDeviceManager/res/values-tr/strings.xml +++ b/packages/CompanionDeviceManager/res/values-tr/strings.xml @@ -59,7 +59,7 @@ <string name="permission_microphone" msgid="2152206421428732949">"Mikrofon"</string> <string name="permission_call_logs" msgid="5546761417694586041">"Arama kayıtları"</string> <string name="permission_nearby_devices" msgid="7530973297737123481">"Yakındaki cihazlar"</string> - <string name="permission_media_routing_control" msgid="5498639511586715253">"Medya çıkışını değiştirin"</string> + <string name="permission_media_routing_control" msgid="5498639511586715253">"Medya çıkışını değiştir"</string> <string name="permission_storage" msgid="6831099350839392343">"Fotoğraflar ve medya"</string> <string name="permission_notifications" msgid="4099418516590632909">"Bildirimler"</string> <string name="permission_app_streaming" msgid="6009695219091526422">"Uygulamalar"</string> diff --git a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml index 2aff2c30939f..3fc61545a713 100644 --- a/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml +++ b/packages/CredentialManager/res/layout/credman_dropdown_presentation_layout.xml @@ -46,6 +46,7 @@ android:paddingEnd="@dimen/autofill_view_right_padding" android:paddingTop="@dimen/autofill_view_top_padding" android:paddingBottom="@dimen/autofill_view_bottom_padding" + android:textDirection="locale" android:orientation="vertical"> <TextView @@ -53,6 +54,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?androidprv:attr/materialColorOnSurface" + android:textDirection="locale" style="@style/autofill.TextTitle"/> <TextView @@ -60,6 +62,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="?androidprv:attr/materialColorOnSurfaceVariant" + android:textDirection="locale" style="@style/autofill.TextSubtitle"/> </LinearLayout> 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-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml index 7b8093e416f0..d9715eefa955 100644 --- a/packages/CredentialManager/res/values-fr-rCA/strings.xml +++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml @@ -48,7 +48,7 @@ <string name="passwords" msgid="5419394230391253816">"mots de passe"</string> <string name="sign_ins" msgid="4710739369149469208">"connexions"</string> <string name="sign_in_info" msgid="2627704710674232328">"renseignements de connexion"</string> - <string name="save_credential_to_title" msgid="3172811692275634301">"Enregistrer la <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> dans"</string> + <string name="save_credential_to_title" msgid="3172811692275634301">"Enregistrer le <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> dans"</string> <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Créer une clé d\'accès sur un autre appareil?"</string> <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Enregistrer le mot de passe sur un autre appareil?"</string> <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Enregistrer l\'authentifiant de connexion sur un autre appareil?"</string> @@ -57,9 +57,9 @@ <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string> <string name="settings" msgid="6536394145760913145">"Paramètres"</string> <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string> - <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string> - <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe"</string> - <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clés d\'accès"</string> + <string name="more_options_usage_passwords_passkeys" msgid="3470113942332934279">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mot(s) de passe • <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clé(s) d\'accès"</string> + <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mot(s) de passe"</string> + <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> clé(s) d\'accès"</string> <string name="more_options_usage_credentials" msgid="1785697001787193984">"<xliff:g id="TOTALCREDENTIALSNUMBER">%1$s</xliff:g> authentifiants"</string> <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clé d\'accès"</string> <string name="another_device" msgid="5147276802037801217">"Un autre appareil"</string> diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml index b239fc35d41e..8617c6a28f43 100644 --- a/packages/CredentialManager/res/values-fr/strings.xml +++ b/packages/CredentialManager/res/values-fr/strings.xml @@ -20,7 +20,7 @@ <string name="app_name" msgid="4539824758261855508">"Gestionnaire d\'identifiants"</string> <string name="string_cancel" msgid="6369133483981306063">"Annuler"</string> <string name="string_continue" msgid="1346732695941131882">"Continuer"</string> - <string name="string_more_options" msgid="2763852250269945472">"Autre façon"</string> + <string name="string_more_options" msgid="2763852250269945472">"Autre option"</string> <string name="string_learn_more" msgid="4541600451688392447">"En savoir plus"</string> <string name="content_description_show_password" msgid="3283502010388521607">"Afficher le mot de passe"</string> <string name="content_description_hide_password" msgid="6841375971631767996">"Masquer le mot de passe"</string> @@ -40,7 +40,7 @@ <string name="choose_provider_title" msgid="8870795677024868108">"Choisissez où enregistrer vos <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string> <string name="choose_provider_body" msgid="4967074531845147434">"Sélectionnez un gestionnaire de mots de passe pour enregistrer vos informations et vous connecter plus rapidement la prochaine fois"</string> <string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Créer une clé d\'accès pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string> - <string name="choose_create_option_password_title" msgid="4481366993598649224">"Enregistrer un mot de passe pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string> + <string name="choose_create_option_password_title" msgid="4481366993598649224">"Enregistrer le mot de passe pour se connecter à <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string> <string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Enregistrer les informations de connexion pour <xliff:g id="APP_NAME">%1$s</xliff:g> ?"</string> <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string> <string name="password" msgid="6738570945182936667">"mot de passe"</string> diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml index 868357f6b42c..630a08a7e903 100644 --- a/packages/CredentialManager/res/values-hy/strings.xml +++ b/packages/CredentialManager/res/values-hy/strings.xml @@ -24,19 +24,19 @@ <string name="string_learn_more" msgid="4541600451688392447">"Իմանալ ավելին"</string> <string name="content_description_show_password" msgid="3283502010388521607">"Ցուցադրել գաղտնաբառը"</string> <string name="content_description_hide_password" msgid="6841375971631767996">"Թաքցնել գաղտնաբառը"</string> - <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Անցաբառերի հետ ավելի ապահով է"</string> - <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Անցաբառերի շնորհիվ դուք բարդ գաղտնաբառեր ստեղծելու կամ հիշելու անհրաժեշտություն չեք ունենա"</string> - <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Անցաբառերը գաղտնագրված թվային բանալիներ են, որոնք ստեղծվում են մատնահետքի, դեմքով ապակողպման կամ էկրանի կողպման օգտագործմամբ"</string> + <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Մուտքի բանալիների հետ ավելի ապահով է"</string> + <string name="passkey_creation_intro_body_password" msgid="8825872426579958200">"Մուտքի բանալիների շնորհիվ դուք բարդ գաղտնաբառեր ստեղծելու կամ հիշելու անհրաժեշտություն չեք ունենա"</string> + <string name="passkey_creation_intro_body_fingerprint" msgid="7331338631826254055">"Մուտքի բանալիները գաղտնագրված թվային բանալիներ են, որոնք ստեղծվում են մատնահետքի, դեմքով ապակողպման կամ էկրանի կողպման օգտագործմամբ"</string> <string name="passkey_creation_intro_body_device" msgid="1203796455762131631">"Դուք կարող եք մուտք գործել այլ սարքերում, քանի որ մուտքի բանալիները պահվում են գաղտնաբառերի կառավարիչում"</string> - <string name="more_about_passkeys_title" msgid="7797903098728837795">"Ավելին՝ անցաբառերի մասին"</string> + <string name="more_about_passkeys_title" msgid="7797903098728837795">"Ավելին՝ մուտքի բանալիների մասին"</string> <string name="passwordless_technology_title" msgid="2497513482056606668">"Գաղտնաբառեր չպահանջող տեխնոլոգիա"</string> <string name="passwordless_technology_detail" msgid="6853928846532955882">"Մուտքի բանալիները ձեզ թույլ են տալիս մուտք գործել առանց գաղտնաբառերի։ Ձեզ պարզապես հարկավոր է օգտագործել ձեր մատնահետքը, դիմաճանաչումը, PIN կոդը կամ նախշը՝ ձեր ինքնությունը հաստատելու և մուտքի բանալի ստեղծելու համար։"</string> <string name="public_key_cryptography_title" msgid="6751970819265298039">"Բաց բանալու կրիպտոգրաֆիա"</string> - <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Ըստ FIDO դաշինքի (որը ներառում է Google-ը, Apple-ը, Microsoft-ը և այլ ընկերություններ) և W3C ստանդարտների՝ անցաբառերն օգտագործում են կրիպտոգրաֆիկ բանալիների զույգ։ Օգտանվան և գաղտնաբառի փոխարեն հավելվածի կամ կայքի համար մենք ստեղծում ենք բաց-փակ բանալիների զույգ։ Փակ բանալին ապահով պահվում է ձեր սարքում կամ գաղտնաբառերի կառավարիչում և հաստատում է ձեր ինքնությունը։ Բաց բանալին փոխանցվում է հավելվածի կամ կայքի սերվերին։ Համապատասխան բանալիների միջոցով կարող եք ակնթարթորեն գրանցվել և մուտք գործել։"</string> + <string name="public_key_cryptography_detail" msgid="6937631710280562213">"Ըստ FIDO դաշինքի (որը ներառում է Google-ը, Apple-ը, Microsoft-ը և այլ ընկերություններ) և W3C ստանդարտների՝ մուտքի բանալիներն օգտագործում են կրիպտոգրաֆիկ բանալիների զույգ։ Օգտանվան և գաղտնաբառի փոխարեն հավելվածի կամ կայքի համար մենք ստեղծում ենք բաց-փակ բանալիների զույգ։ Փակ բանալին ապահով պահվում է ձեր սարքում կամ գաղտնաբառերի կառավարիչում և հաստատում է ձեր ինքնությունը։ Բաց բանալին փոխանցվում է հավելվածի կամ կայքի սերվերին։ Համապատասխան բանալիների միջոցով կարող եք ակնթարթորեն գրանցվել և մուտք գործել։"</string> <string name="improved_account_security_title" msgid="1069841917893513424">"Հաշվի բարելավված անվտանգություն"</string> <string name="improved_account_security_detail" msgid="9123750251551844860">"Յուրաքանչյուր բանալի բացառապես կապված է հավելվածի կամ կայքի հետ, որի համար այն ստեղծվել է, ուստի դուք երբեք չեք կարող սխալմամբ մուտք գործել կեղծ հավելված կամ կայք։ Բացի այդ՝ սերվերներում պահվում են միայն բաց բանալիներ, ինչը զգալիորեն դժվարացնում է կոտրումը։"</string> <string name="seamless_transition_title" msgid="5335622196351371961">"Սահուն անցում"</string> - <string name="seamless_transition_detail" msgid="4475509237171739843">"Թեև մենք առանց գաղտնաբառերի ապագայի ճանապարհին ենք, դրանք դեռ հասանելի կլինեն անցաբառերի հետ մեկտեղ։"</string> + <string name="seamless_transition_detail" msgid="4475509237171739843">"Թեև մենք առանց գաղտնաբառերի ապագայի ճանապարհին ենք, դրանք դեռ հասանելի կլինեն մուտքի բանալիների հետ մեկտեղ։"</string> <string name="choose_provider_title" msgid="8870795677024868108">"Նշեք, թե որտեղ եք ուզում պահել ձեր <xliff:g id="CREATETYPES">%1$s</xliff:g>ը"</string> <string name="choose_provider_body" msgid="4967074531845147434">"Ընտրեք գաղտնաբառերի կառավարիչ՝ ձեր տեղեկությունները պահելու և հաջորդ անգամ ավելի արագ մուտք գործելու համար"</string> <string name="choose_create_option_passkey_title" msgid="8762295821604276511">"Ստեղծե՞լ մուտքի բանալի՝ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելված մուտք գործելու համար"</string> @@ -44,7 +44,7 @@ <string name="choose_create_option_sign_in_title" msgid="7092914088455358079">"Պահե՞լ «<xliff:g id="APP_NAME">%1$s</xliff:g>» հավելվածի մուտքի տվյալները"</string> <string name="passkey" msgid="632353688396759522">"մուտքի բանալի"</string> <string name="password" msgid="6738570945182936667">"գաղտնաբառ"</string> - <string name="passkeys" msgid="5733880786866559847">"անցաբառեր"</string> + <string name="passkeys" msgid="5733880786866559847">"մուտքի բանալիներ"</string> <string name="passwords" msgid="5419394230391253816">"գաղտնաբառեր"</string> <string name="sign_ins" msgid="4710739369149469208">"մուտք"</string> <string name="sign_in_info" msgid="2627704710674232328">"մուտքի տվյալներ"</string> @@ -61,7 +61,7 @@ <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="passkey_before_subtitle" msgid="2448119456208647444">"Մուտքի բանալի"</string> <string name="another_device" msgid="5147276802037801217">"Այլ սարք"</string> <string name="other_password_manager" msgid="565790221427004141">"Գաղտնաբառերի այլ կառավարիչներ"</string> <string name="close_sheet" msgid="1393792015338908262">"Փակել թերթը"</string> diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml index 4224da6e70a4..452565cb95a9 100644 --- a/packages/CredentialManager/res/values-ka/strings.xml +++ b/packages/CredentialManager/res/values-ka/strings.xml @@ -74,7 +74,7 @@ <string name="get_dialog_description_single_tap" msgid="2797059565126030879">"გამოიყენეთ თქვენი ეკრანის დაბლოკვის ფუნქცია <xliff:g id="APP_NAME">%1$s</xliff:g>-ში <xliff:g id="USERNAME">%2$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/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml index 2fd31ee90cdd..c3bfc4f09c59 100644 --- a/packages/CredentialManager/res/values-kk/strings.xml +++ b/packages/CredentialManager/res/values-kk/strings.xml @@ -90,7 +90,7 @@ <string name="locked_credential_entry_label_subtext_no_sign_in" msgid="8131725029983174901">"Кіру ақпараты жоқ."</string> <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> аккаунтында кіру туралы ешқандай ақпарат жоқ."</string> <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Кіру әрекеттерін басқару"</string> - <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Басқа құрылғыдан жасау"</string> + <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"Басқа құрылғыдан"</string> <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Басқа құрылғыны пайдалану"</string> <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы сұрауды тоқтатты."</string> <string name="dropdown_presentation_more_sign_in_options_text" msgid="1693727354272417902">"Кіру опциялары"</string> diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml index 07775e007835..7ac1d1ac4d94 100644 --- a/packages/CredentialManager/res/values-ne/strings.xml +++ b/packages/CredentialManager/res/values-ne/strings.xml @@ -91,7 +91,7 @@ <string name="no_sign_in_info_in" msgid="2641118151920288356">"<xliff:g id="SOURCE">%1$s</xliff:g> मा साइन इन गर्नेसम्बन्धी कुनै पनि जानकारी छैन"</string> <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"साइन इनसम्बन्धी विकल्पहरू व्यवस्थापन गर्नुहोस्"</string> <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"अर्को डिभाइसका लागि"</string> - <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"अर्कै डिभाइस प्रयोग गरी हेर्नुहोस्"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"अर्कै डिभाइस प्रयोग गर्नुहोस्"</string> <string name="request_cancelled_by" msgid="3735222326886267820">"<xliff:g id="APP_NAME">%1$s</xliff:g> ले अनुरोध रद्द गरेको छ"</string> <string name="dropdown_presentation_more_sign_in_options_text" msgid="1693727354272417902">"साइन इनसम्बन्धी विकल्पहरू"</string> <string name="more_options_content_description" msgid="1323427365788198808">"थप"</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-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml index a3476d927da7..9186c59caaee 100644 --- a/packages/CredentialManager/res/values-pt-rPT/strings.xml +++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml @@ -91,7 +91,7 @@ <string name="no_sign_in_info_in" msgid="2641118151920288356">"Sem informações de início de sessão em <xliff:g id="SOURCE">%1$s</xliff:g>"</string> <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"Faça a gestão dos inícios de sessão"</string> <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"De outro dispositivo"</string> - <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use um dispositivo diferente"</string> + <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"Use outro diferente"</string> <string name="request_cancelled_by" msgid="3735222326886267820">"Pedido cancelado pela app <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="dropdown_presentation_more_sign_in_options_text" msgid="1693727354272417902">"Opções de início de sessão"</string> <string name="more_options_content_description" msgid="1323427365788198808">"Mais"</string> diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml index 8e6b32ccd3ca..5b0be91addd1 100644 --- a/packages/CredentialManager/res/values-sk/strings.xml +++ b/packages/CredentialManager/res/values-sk/strings.xml @@ -48,7 +48,7 @@ <string name="passwords" msgid="5419394230391253816">"heslá"</string> <string name="sign_ins" msgid="4710739369149469208">"prihlasovacie údaje"</string> <string name="sign_in_info" msgid="2627704710674232328">"prihlasovacie údaje"</string> - <string name="save_credential_to_title" msgid="3172811692275634301">"Uložiť <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> do"</string> + <string name="save_credential_to_title" msgid="3172811692275634301">"Kam uložiť <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>?"</string> <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Chcete vytvoriť prístupový kľúč v inom zariadení?"</string> <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Chcete uložiť heslo v inom zariadení?"</string> <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Chcete uložiť prihlasovacie údaje v inom zariadení?"</string> diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml index cd5b7bb9ff1f..e7461308bab5 100644 --- a/packages/CredentialManager/res/values-uk/strings.xml +++ b/packages/CredentialManager/res/values-uk/strings.xml @@ -48,7 +48,7 @@ <string name="passwords" msgid="5419394230391253816">"паролі"</string> <string name="sign_ins" msgid="4710739369149469208">"дані для входу"</string> <string name="sign_in_info" msgid="2627704710674232328">"дані для входу"</string> - <string name="save_credential_to_title" msgid="3172811692275634301">"Зберегти <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g> в"</string> + <string name="save_credential_to_title" msgid="3172811692275634301">"Де слід зберегти <xliff:g id="CREDENTIALTYPES">%1$s</xliff:g>?"</string> <string name="create_passkey_in_other_device_title" msgid="2360053098931886245">"Створити ключ доступу на іншому пристрої?"</string> <string name="save_password_on_other_device_title" msgid="5829084591948321207">"Зберегти пароль на іншому пристрої?"</string> <string name="save_sign_in_on_other_device_title" msgid="2827990118560134692">"Зберегти дані для входу на іншому пристрої?"</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/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml index 46a51389214f..0bae63a1c525 100644 --- a/packages/CredentialManager/res/values/strings.xml +++ b/packages/CredentialManager/res/values/strings.xml @@ -24,7 +24,7 @@ <string name="string_cancel">Cancel</string> <!-- This is a label for a button that takes user to the next screen. [CHAR LIMIT=20] --> <string name="string_continue">Continue</string> - <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=20] --> + <!-- This is a label for a button that leads to a holistic view of all different options where the user can save their new app credential. [CHAR LIMIT=30] --> <string name="string_more_options">Save another way</string> <!-- This is a label for a button that links to additional information about passkeys. [CHAR LIMIT=20] --> <string name="string_learn_more">Learn more</string> @@ -174,4 +174,4 @@ <!-- Text shown in the dropdown presentation to select more sign in options. [CHAR LIMIT=120] --> <string name="dropdown_presentation_more_sign_in_options_text">Sign-in options</string> <string name="more_options_content_description">More</string> -</resources>
\ No newline at end of file +</resources> diff --git a/packages/CredentialManager/res/xml/autofill_service_configuration.xml b/packages/CredentialManager/res/xml/autofill_service_configuration.xml index 25cc094fa44e..0151add1c8e8 100644 --- a/packages/CredentialManager/res/xml/autofill_service_configuration.xml +++ b/packages/CredentialManager/res/xml/autofill_service_configuration.xml @@ -5,6 +5,6 @@ Note: This file is ignored for devices older that API 31 See https://developer.android.com/about/versions/12/backup-restore --> -<autofill-service-configuration +<autofill-service xmlns:android="http://schemas.android.com/apk/res/android" android:supportsInlineSuggestions="true"/>
\ No newline at end of file diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index d4a81109e53c..7bc25ed81089 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -174,11 +174,8 @@ class CredentialSelectorViewModel( onUserCancel() } else { Log.d(Constants.LOG_TAG, "The provider activity was cancelled," + - " re-displaying our UI.") - uiState = uiState.copy( - selectedEntry = null, - providerActivityState = ProviderActivityState.NOT_APPLICABLE, - ) + " re-displaying our UI.") + resetUiStateForReLaunch() } } else { if (entry != null) { @@ -202,6 +199,15 @@ class CredentialSelectorViewModel( } } + // Resets UI states for any situation that re-launches the UI + private fun resetUiStateForReLaunch() { + onBiometricPromptStateChange(BiometricPromptState.INACTIVE) + uiState = uiState.copy( + selectedEntry = null, + providerActivityState = ProviderActivityState.NOT_APPLICABLE, + ) + } + fun onLastLockedAuthEntryNotFoundError() { Log.d(Constants.LOG_TAG, "Unable to find the last unlocked entry") onInternalError() @@ -502,4 +508,4 @@ class CredentialSelectorViewModel( fun logUiEvent(uiEventEnum: UiEventEnum) { this.uiMetrics.log(uiEventEnum, credManRepo.requestInfo?.packageName) } -}
\ No newline at end of file +} 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/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt index 04175335b9db..473094cc1308 100644 --- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt +++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt @@ -22,6 +22,7 @@ import com.android.credentialmanager.CredentialSelectorUiState import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries import com.android.credentialmanager.model.CredentialType import com.android.credentialmanager.model.get.CredentialEntryInfo +import java.time.Instant fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get { val accounts = providerInfos @@ -67,4 +68,4 @@ fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get { val comparator = compareBy<CredentialEntryInfo> { entryInfo -> // Passkey type always go first entryInfo.credentialType.let { if (it == CredentialType.PASSKEY) 0 else 1 } -}.thenByDescending { it.lastUsedTimeMillis ?: 0 } +}.thenByDescending { it.lastUsedTimeMillis ?: Instant.EPOCH } 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/InputDevices/res/raw/keyboard_layout_french_ca.kcm b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm index 03b5c19f8184..723c187d818f 100644 --- a/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm +++ b/packages/InputDevices/res/raw/keyboard_layout_french_ca.kcm @@ -348,13 +348,13 @@ key COMMA { label: ',' base: ',' shift: '\'' - ralt: '_' + ralt: '\u00af' } key PERIOD { label: '.' base: '.' - ralt: '-' + ralt: '\u00ad' } key SLASH { diff --git a/packages/InputDevices/res/values-af/strings.xml b/packages/InputDevices/res/values-af/strings.xml index 232df248690a..93691400737a 100644 --- a/packages/InputDevices/res/values-af/strings.xml +++ b/packages/InputDevices/res/values-af/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongools"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgies"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thais (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-am/strings.xml b/packages/InputDevices/res/values-am/strings.xml index 9a8bd375d809..16f64379be41 100644 --- a/packages/InputDevices/res/values-am/strings.xml +++ b/packages/InputDevices/res/values-am/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ሞንጎሊያኛ"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ጂዮርጂያኛ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ታይላንድኛ (ኬድማኒ)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ታይላንድኛ (ፓታሾት)"</string> </resources> diff --git a/packages/InputDevices/res/values-ar/strings.xml b/packages/InputDevices/res/values-ar/strings.xml index e79519ac1df7..93223bac47e8 100644 --- a/packages/InputDevices/res/values-ar/strings.xml +++ b/packages/InputDevices/res/values-ar/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"المنغولية"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"الجورجية"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"التايلاندية (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"التايلاندية (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-as/strings.xml b/packages/InputDevices/res/values-as/strings.xml index 802b13eacfe8..c57b59190f6c 100644 --- a/packages/InputDevices/res/values-as/strings.xml +++ b/packages/InputDevices/res/values-as/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"থাই (কেডমানি)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"থাই (পাটাচ’টে)"</string> </resources> diff --git a/packages/InputDevices/res/values-az/strings.xml b/packages/InputDevices/res/values-az/strings.xml index 4a8e7ff7092e..9c6bdb3925b9 100644 --- a/packages/InputDevices/res/values-az/strings.xml +++ b/packages/InputDevices/res/values-az/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Monqol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gürcü"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tay (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tay (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-b+sr+Latn/strings.xml b/packages/InputDevices/res/values-b+sr+Latn/strings.xml index 9126a74d4f1c..80ecff5c7c6a 100644 --- a/packages/InputDevices/res/values-b+sr+Latn/strings.xml +++ b/packages/InputDevices/res/values-b+sr+Latn/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolska"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzijska"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajski (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajski (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-be/strings.xml b/packages/InputDevices/res/values-be/strings.xml index 4dd59bf70716..c5aa66f4e273 100644 --- a/packages/InputDevices/res/values-be/strings.xml +++ b/packages/InputDevices/res/values-be/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Мангольская"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузінская"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайская (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайская (Патачотэ)"</string> </resources> diff --git a/packages/InputDevices/res/values-bg/strings.xml b/packages/InputDevices/res/values-bg/strings.xml index 3dbd6f735644..1260d6af08c2 100644 --- a/packages/InputDevices/res/values-bg/strings.xml +++ b/packages/InputDevices/res/values-bg/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"монголски"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузински"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"тайландски (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"тайландски (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-bn/strings.xml b/packages/InputDevices/res/values-bn/strings.xml index 0d8e5d855409..a038da9eb4d0 100644 --- a/packages/InputDevices/res/values-bn/strings.xml +++ b/packages/InputDevices/res/values-bn/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"মঙ্গোলিয়ান"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"জর্জিয়ান"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"থাই (কেডমানি)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"থাই (পাট্টাচোটে)"</string> </resources> diff --git a/packages/InputDevices/res/values-bs/strings.xml b/packages/InputDevices/res/values-bs/strings.xml index bb12f0049d65..12e93bcbdd92 100644 --- a/packages/InputDevices/res/values-bs/strings.xml +++ b/packages/InputDevices/res/values-bs/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolski"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzijski"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajlandski (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajlandski (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ca/strings.xml b/packages/InputDevices/res/values-ca/strings.xml index 86b6421fa706..8a1e059c0fd9 100644 --- a/packages/InputDevices/res/values-ca/strings.xml +++ b/packages/InputDevices/res/values-ca/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgià"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-cs/strings.xml b/packages/InputDevices/res/values-cs/strings.xml index 7832e6eb215f..9ee17e106729 100644 --- a/packages/InputDevices/res/values-cs/strings.xml +++ b/packages/InputDevices/res/values-cs/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolština"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzínština"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thajština (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thajština (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-da/strings.xml b/packages/InputDevices/res/values-da/strings.xml index 51cb1ae00747..db75d3eac790 100644 --- a/packages/InputDevices/res/values-da/strings.xml +++ b/packages/InputDevices/res/values-da/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolsk"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisk"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-de/strings.xml b/packages/InputDevices/res/values-de/strings.xml index c4de5a8817e6..3db695e72763 100644 --- a/packages/InputDevices/res/values-de/strings.xml +++ b/packages/InputDevices/res/values-de/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolisch"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisch"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thailändisch (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thailändisch (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-el/strings.xml b/packages/InputDevices/res/values-el/strings.xml index 48f7c0256b79..cb7aa2c27bec 100644 --- a/packages/InputDevices/res/values-el/strings.xml +++ b/packages/InputDevices/res/values-el/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Μογγολικά"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Γεωργιανά"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Ταϊλανδικά (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Ταϊλανδικά (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-en-rAU/strings.xml b/packages/InputDevices/res/values-en-rAU/strings.xml index 2d373708052e..d113201205a7 100644 --- a/packages/InputDevices/res/values-en-rAU/strings.xml +++ b/packages/InputDevices/res/values-en-rAU/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-en-rCA/strings.xml b/packages/InputDevices/res/values-en-rCA/strings.xml index f863fe93146f..cae7f00f6745 100644 --- a/packages/InputDevices/res/values-en-rCA/strings.xml +++ b/packages/InputDevices/res/values-en-rCA/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-en-rGB/strings.xml b/packages/InputDevices/res/values-en-rGB/strings.xml index 2d373708052e..d113201205a7 100644 --- a/packages/InputDevices/res/values-en-rGB/strings.xml +++ b/packages/InputDevices/res/values-en-rGB/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-en-rIN/strings.xml b/packages/InputDevices/res/values-en-rIN/strings.xml index 2d373708052e..d113201205a7 100644 --- a/packages/InputDevices/res/values-en-rIN/strings.xml +++ b/packages/InputDevices/res/values-en-rIN/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-en-rXC/strings.xml b/packages/InputDevices/res/values-en-rXC/strings.xml index 0a4344bf7300..71c84da0ae9e 100644 --- a/packages/InputDevices/res/values-en-rXC/strings.xml +++ b/packages/InputDevices/res/values-en-rXC/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-es-rUS/strings.xml b/packages/InputDevices/res/values-es-rUS/strings.xml index 216e91fa97be..7490f7d546bd 100644 --- a/packages/InputDevices/res/values-es-rUS/strings.xml +++ b/packages/InputDevices/res/values-es-rUS/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandés (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandés (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-es/strings.xml b/packages/InputDevices/res/values-es/strings.xml index 648b3d2dde97..22b8cda2d531 100644 --- a/packages/InputDevices/res/values-es/strings.xml +++ b/packages/InputDevices/res/values-es/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandés (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandés (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-et/strings.xml b/packages/InputDevices/res/values-et/strings.xml index 0aba840481b7..34fd3d7a6978 100644 --- a/packages/InputDevices/res/values-et/strings.xml +++ b/packages/InputDevices/res/values-et/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongoli"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruusia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-eu/strings.xml b/packages/InputDevices/res/values-eu/strings.xml index c1a0dac09cca..15535fd32da8 100644 --- a/packages/InputDevices/res/values-eu/strings.xml +++ b/packages/InputDevices/res/values-eu/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongoliarra"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiarra"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thailandiarra (kedmanee-a)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thailandiarra (pattachote-a)"</string> </resources> diff --git a/packages/InputDevices/res/values-fa/strings.xml b/packages/InputDevices/res/values-fa/strings.xml index 4fba26d8ecea..11280dd26c9a 100644 --- a/packages/InputDevices/res/values-fa/strings.xml +++ b/packages/InputDevices/res/values-fa/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"مغولی"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"گرجستانی"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"تایلندی (کدمانی)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"تایلندی (پاتاچوته)"</string> </resources> diff --git a/packages/InputDevices/res/values-fi/strings.xml b/packages/InputDevices/res/values-fi/strings.xml index b8199a80ab1f..6c6d4cf1c176 100644 --- a/packages/InputDevices/res/values-fi/strings.xml +++ b/packages/InputDevices/res/values-fi/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongoli"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thai (kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thai (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-fr-rCA/strings.xml b/packages/InputDevices/res/values-fr-rCA/strings.xml index f375dadb400d..5c931cf178dd 100644 --- a/packages/InputDevices/res/values-fr-rCA/strings.xml +++ b/packages/InputDevices/res/values-fr-rCA/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Géorgien"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thaï (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thaï (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-fr/strings.xml b/packages/InputDevices/res/values-fr/strings.xml index e19d9dc4a6b3..13236756030e 100644 --- a/packages/InputDevices/res/values-fr/strings.xml +++ b/packages/InputDevices/res/values-fr/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Géorgien"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thaï (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thaï (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-gl/strings.xml b/packages/InputDevices/res/values-gl/strings.xml index c878e1e83655..cedff5b7c545 100644 --- a/packages/InputDevices/res/values-gl/strings.xml +++ b/packages/InputDevices/res/values-gl/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Xeorxiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandés (kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandés (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-gu/strings.xml b/packages/InputDevices/res/values-gu/strings.xml index d5fe48cf7cc3..cbd4c40c00cd 100644 --- a/packages/InputDevices/res/values-gu/strings.xml +++ b/packages/InputDevices/res/values-gu/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"મોંગોલિયન"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"જ્યોર્જિઅન"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"થાઇ (કેડમાની)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"થાઇ (પટ્ટાશોટે)"</string> </resources> diff --git a/packages/InputDevices/res/values-hi/strings.xml b/packages/InputDevices/res/values-hi/strings.xml index 3fa45b13a5a1..7e3df8200b1e 100644 --- a/packages/InputDevices/res/values-hi/strings.xml +++ b/packages/InputDevices/res/values-hi/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"मंगोलियन"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"जॉर्जियन कीबोर्ड का लेआउट"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"थाई (केडमेनी)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"थाई (पटैचोटे)"</string> </resources> diff --git a/packages/InputDevices/res/values-hr/strings.xml b/packages/InputDevices/res/values-hr/strings.xml index 409a32154613..ba3dc51a4543 100644 --- a/packages/InputDevices/res/values-hr/strings.xml +++ b/packages/InputDevices/res/values-hr/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolski"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzijska"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajlandski (kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tajski (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-hu/strings.xml b/packages/InputDevices/res/values-hu/strings.xml index 3131bc54bde7..c42e009d1d71 100644 --- a/packages/InputDevices/res/values-hu/strings.xml +++ b/packages/InputDevices/res/values-hu/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"grúz"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thai (kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thai (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-hy/strings.xml b/packages/InputDevices/res/values-hy/strings.xml index 2307a9b58be4..d85cf9dcb2b9 100644 --- a/packages/InputDevices/res/values-hy/strings.xml +++ b/packages/InputDevices/res/values-hy/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Մոնղոլերեն"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"վրացերեն"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"թայերեն (քեդմանի)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"թայերեն (պատաչոտ)"</string> </resources> diff --git a/packages/InputDevices/res/values-in/strings.xml b/packages/InputDevices/res/values-in/strings.xml index 69ba58adb152..d504540c6c9c 100644 --- a/packages/InputDevices/res/values-in/strings.xml +++ b/packages/InputDevices/res/values-in/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolia"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-is/strings.xml b/packages/InputDevices/res/values-is/strings.xml index 5107503f9d99..637874c8b467 100644 --- a/packages/InputDevices/res/values-is/strings.xml +++ b/packages/InputDevices/res/values-is/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongólska"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgíska"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Taílenskt (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Taílenskt (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-it/strings.xml b/packages/InputDevices/res/values-it/strings.xml index 740cf376db3f..eed8316c5af1 100644 --- a/packages/InputDevices/res/values-it/strings.xml +++ b/packages/InputDevices/res/values-it/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolo"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-iw/strings.xml b/packages/InputDevices/res/values-iw/strings.xml index 6badc3f8b0e4..8cfe2cbf3e79 100644 --- a/packages/InputDevices/res/values-iw/strings.xml +++ b/packages/InputDevices/res/values-iw/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"מונגולית"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"גיאורגית"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"תאית (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"תאית (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ja/strings.xml b/packages/InputDevices/res/values-ja/strings.xml index 967688644697..d1b334b0f45a 100644 --- a/packages/InputDevices/res/values-ja/strings.xml +++ b/packages/InputDevices/res/values-ja/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"モンゴル語"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ジョージア語"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"タイ語(Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"タイ語(Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ka/strings.xml b/packages/InputDevices/res/values-ka/strings.xml index 93735c9c2af3..8928f684b8b6 100644 --- a/packages/InputDevices/res/values-ka/strings.xml +++ b/packages/InputDevices/res/values-ka/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"მონღოლური"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ქართული"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ტაილანდური (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ტაილანდური (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-kk/strings.xml b/packages/InputDevices/res/values-kk/strings.xml index 603b6c7f72ae..cf3d3ca78c51 100644 --- a/packages/InputDevices/res/values-kk/strings.xml +++ b/packages/InputDevices/res/values-kk/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Моңғол"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузин"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тай (кедмани)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тай (паттачот)"</string> </resources> diff --git a/packages/InputDevices/res/values-km/strings.xml b/packages/InputDevices/res/values-km/strings.xml index 0f3832e013b4..53eb6f53fbea 100644 --- a/packages/InputDevices/res/values-km/strings.xml +++ b/packages/InputDevices/res/values-km/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"មុងហ្គោលី"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ហ្សកហ្ស៊ី"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ថៃ (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ថៃ (ប៉ាតាឈោត)"</string> </resources> diff --git a/packages/InputDevices/res/values-kn/strings.xml b/packages/InputDevices/res/values-kn/strings.xml index 96b92946ab22..c743a6e1c65f 100644 --- a/packages/InputDevices/res/values-kn/strings.xml +++ b/packages/InputDevices/res/values-kn/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ಮಂಗೋಲಿಯನ್"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ಜಾರ್ಜಿಯನ್"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ಥಾಯ್ (ಕೆಡ್ಮನೀ)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ಥಾಯ್ (ಪಟ್ಟಚೋಟ್)"</string> </resources> diff --git a/packages/InputDevices/res/values-ko/strings.xml b/packages/InputDevices/res/values-ko/strings.xml index 9956f26ec93f..0e375dd743f3 100644 --- a/packages/InputDevices/res/values-ko/strings.xml +++ b/packages/InputDevices/res/values-ko/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"몽골어"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"조지아어"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"태국어(Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"태국어(Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ky/strings.xml b/packages/InputDevices/res/values-ky/strings.xml index 0b1ab5a71e11..dad5c9184097 100644 --- a/packages/InputDevices/res/values-ky/strings.xml +++ b/packages/InputDevices/res/values-ky/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Монголчо"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузинче"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайча (Kedmanee баскычтобу)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайча (Pattachote баскычтобу)"</string> </resources> diff --git a/packages/InputDevices/res/values-lo/strings.xml b/packages/InputDevices/res/values-lo/strings.xml index 3effea78fb9a..0794bde0a9e2 100644 --- a/packages/InputDevices/res/values-lo/strings.xml +++ b/packages/InputDevices/res/values-lo/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ມອງໂກລຽນ"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ຈໍຈຽນ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ໄທ (ເກດມະນີ)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ໄທ (ປັດຕະໂຊຕິ)"</string> </resources> diff --git a/packages/InputDevices/res/values-lt/strings.xml b/packages/InputDevices/res/values-lt/strings.xml index 82cf221540e0..0cceec7cce7c 100644 --- a/packages/InputDevices/res/values-lt/strings.xml +++ b/packages/InputDevices/res/values-lt/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolų"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzinų"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tajų („Kedmanee“)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tajų („Pattachote“)"</string> </resources> diff --git a/packages/InputDevices/res/values-lv/strings.xml b/packages/InputDevices/res/values-lv/strings.xml index 952f8030bd47..9b528549b79f 100644 --- a/packages/InputDevices/res/values-lv/strings.xml +++ b/packages/InputDevices/res/values-lv/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongoļu"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzīnu"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Taju valoda (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Taju (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-mk/strings.xml b/packages/InputDevices/res/values-mk/strings.xml index 27096895a52d..4e8be4637ea5 100644 --- a/packages/InputDevices/res/values-mk/strings.xml +++ b/packages/InputDevices/res/values-mk/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"монголски"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузиски"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"тајландски (кедмани)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"тајландски (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ml/strings.xml b/packages/InputDevices/res/values-ml/strings.xml index 8559a21b3ed2..4b2a5fd40e7b 100644 --- a/packages/InputDevices/res/values-ml/strings.xml +++ b/packages/InputDevices/res/values-ml/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"മംഗോളിയൻ"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ജോര്ജ്ജിയൻ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"തായ് (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"തായ് (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-mn/strings.xml b/packages/InputDevices/res/values-mn/strings.xml index 397c947e0297..a7a1799e4345 100644 --- a/packages/InputDevices/res/values-mn/strings.xml +++ b/packages/InputDevices/res/values-mn/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Монгол"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Гүрж"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тай (кедмани)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тай (паттачоте)"</string> </resources> diff --git a/packages/InputDevices/res/values-mr/strings.xml b/packages/InputDevices/res/values-mr/strings.xml index a818afec2aec..5e4baa05e77e 100644 --- a/packages/InputDevices/res/values-mr/strings.xml +++ b/packages/InputDevices/res/values-mr/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"मंगोलियन"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"जॉर्जियन"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"थाई (केडमानी)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"थाई (पट्टाचोटे)"</string> </resources> diff --git a/packages/InputDevices/res/values-ms/strings.xml b/packages/InputDevices/res/values-ms/strings.xml index 94ec21acf3dd..9e4c19098af8 100644 --- a/packages/InputDevices/res/values-ms/strings.xml +++ b/packages/InputDevices/res/values-ms/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Bahasa Mongolia"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Bahasa Georgia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-my/strings.xml b/packages/InputDevices/res/values-my/strings.xml index babd1ea3440b..5dbdc7030abe 100644 --- a/packages/InputDevices/res/values-my/strings.xml +++ b/packages/InputDevices/res/values-my/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"မွန်ဂိုလီးယား"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ဂျော်ဂျီယာ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ထိုင်း (ကတ်မနီး)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ထိုင်း (ပတ်တာချုတ်)"</string> </resources> diff --git a/packages/InputDevices/res/values-nb/strings.xml b/packages/InputDevices/res/values-nb/strings.xml index 5c465da60905..1e9af3960c4a 100644 --- a/packages/InputDevices/res/values-nb/strings.xml +++ b/packages/InputDevices/res/values-nb/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolsk"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisk"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ne/strings.xml b/packages/InputDevices/res/values-ne/strings.xml index c32e45855817..ab22576177c7 100644 --- a/packages/InputDevices/res/values-ne/strings.xml +++ b/packages/InputDevices/res/values-ne/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"मङ्गोलियाली"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"जर्जियाली"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"थाई (केडमानी)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"थाई (पत्ताचोते)"</string> </resources> diff --git a/packages/InputDevices/res/values-nl/strings.xml b/packages/InputDevices/res/values-nl/strings.xml index 1d399ae4d07a..d28ee9b19157 100644 --- a/packages/InputDevices/res/values-nl/strings.xml +++ b/packages/InputDevices/res/values-nl/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongools"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgisch"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-or/strings.xml b/packages/InputDevices/res/values-or/strings.xml index 0d0fbafe3424..e92c15566dc4 100644 --- a/packages/InputDevices/res/values-or/strings.xml +++ b/packages/InputDevices/res/values-or/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ମଙ୍ଗୋଲିଆନ୍"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ଜର୍ଜିଆନ୍"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ଥାଇ (କେଡମାନି)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ଥାଇ (ପାଟ୍ଟାଚୋଟେ)"</string> </resources> diff --git a/packages/InputDevices/res/values-pa/strings.xml b/packages/InputDevices/res/values-pa/strings.xml index fbf50aeadb15..f766297c0d35 100644 --- a/packages/InputDevices/res/values-pa/strings.xml +++ b/packages/InputDevices/res/values-pa/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ਮੰਗੋਲੀਆਈ"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ਜਾਰਜੀਆਈ"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ਥਾਈ (ਕੇਦਮਨੀ)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ਥਾਈ (ਪੈਟਾਸ਼ੋਟੇ)"</string> </resources> diff --git a/packages/InputDevices/res/values-pl/strings.xml b/packages/InputDevices/res/values-pl/strings.xml index 6bf6f4849d88..e202463b97de 100644 --- a/packages/InputDevices/res/values-pl/strings.xml +++ b/packages/InputDevices/res/values-pl/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolski"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruziński"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajski (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajski (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-pt-rBR/strings.xml b/packages/InputDevices/res/values-pt-rBR/strings.xml index e2bad4fee77d..4a0c3be58def 100644 --- a/packages/InputDevices/res/values-pt-rBR/strings.xml +++ b/packages/InputDevices/res/values-pt-rBR/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-pt-rPT/strings.xml b/packages/InputDevices/res/values-pt-rPT/strings.xml index 7ade82f54d8f..c54b620ee6fd 100644 --- a/packages/InputDevices/res/values-pt-rPT/strings.xml +++ b/packages/InputDevices/res/values-pt-rPT/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-pt/strings.xml b/packages/InputDevices/res/values-pt/strings.xml index e2bad4fee77d..4a0c3be58def 100644 --- a/packages/InputDevices/res/values-pt/strings.xml +++ b/packages/InputDevices/res/values-pt/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiano"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tailandês (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tailandês (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ro/strings.xml b/packages/InputDevices/res/values-ro/strings.xml index 248b3adfd207..d91635bff4a8 100644 --- a/packages/InputDevices/res/values-ro/strings.xml +++ b/packages/InputDevices/res/values-ro/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolă"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgiană"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thailandeză (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thailandeză (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ru/strings.xml b/packages/InputDevices/res/values-ru/strings.xml index 171b713c1a87..da1a83a59cf3 100644 --- a/packages/InputDevices/res/values-ru/strings.xml +++ b/packages/InputDevices/res/values-ru/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"монгольский"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузинский"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайский (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайский (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-si/strings.xml b/packages/InputDevices/res/values-si/strings.xml index 27b031bcc440..97aed6286a8c 100644 --- a/packages/InputDevices/res/values-si/strings.xml +++ b/packages/InputDevices/res/values-si/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"මොන්ගෝලියානු"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ජෝර්ජියානු"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"තායි (කෙඩ්මනී)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"තායි (පට්ටචෝටේ)"</string> </resources> diff --git a/packages/InputDevices/res/values-sk/strings.xml b/packages/InputDevices/res/values-sk/strings.xml index 7d4315b14877..6f387ad2c6bb 100644 --- a/packages/InputDevices/res/values-sk/strings.xml +++ b/packages/InputDevices/res/values-sk/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolské"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzínske"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"thajčina (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thajčina (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-sl/strings.xml b/packages/InputDevices/res/values-sl/strings.xml index fc8f1466b1da..32ca0ad34c02 100644 --- a/packages/InputDevices/res/values-sl/strings.xml +++ b/packages/InputDevices/res/values-sl/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongolščina"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"gruzinščina"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"tajščina (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"tajščina (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-sq/strings.xml b/packages/InputDevices/res/values-sq/strings.xml index 65cec148fa62..c33ba4af578d 100644 --- a/packages/InputDevices/res/values-sq/strings.xml +++ b/packages/InputDevices/res/values-sq/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolisht"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gjeorgjisht"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tajlandisht (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tajlandisht (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-sr/strings.xml b/packages/InputDevices/res/values-sr/strings.xml index 2f500138f7cb..0b434d7c8cb1 100644 --- a/packages/InputDevices/res/values-sr/strings.xml +++ b/packages/InputDevices/res/values-sr/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"монголска"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"грузијска"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"тајски (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"тајски (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-sv/strings.xml b/packages/InputDevices/res/values-sv/strings.xml index f29b6655f587..3d08415081fe 100644 --- a/packages/InputDevices/res/values-sv/strings.xml +++ b/packages/InputDevices/res/values-sv/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"mongoliska"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"georgiska"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"thailändska (pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-sw/strings.xml b/packages/InputDevices/res/values-sw/strings.xml index ade2c284cf8d..42714a55530f 100644 --- a/packages/InputDevices/res/values-sw/strings.xml +++ b/packages/InputDevices/res/values-sw/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Kimongolia"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Kijojia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Kithai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Kitai (Kipatachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-ta/strings.xml b/packages/InputDevices/res/values-ta/strings.xml index 14a17adaac26..f8bc751c3ca5 100644 --- a/packages/InputDevices/res/values-ta/strings.xml +++ b/packages/InputDevices/res/values-ta/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"மங்கோலியன்"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ஜார்ஜியன்"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"தாய் (கேட்மேனி)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"தாய் (பட்டாசொட்டே)"</string> </resources> diff --git a/packages/InputDevices/res/values-te/strings.xml b/packages/InputDevices/res/values-te/strings.xml index 676dd924d601..2c1c1f8021fa 100644 --- a/packages/InputDevices/res/values-te/strings.xml +++ b/packages/InputDevices/res/values-te/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"మంగోలియన్"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"జార్జియన్"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"థాయ్ (కెడ్మనీ)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"థాయ్ (పత్తచోత్)"</string> </resources> diff --git a/packages/InputDevices/res/values-th/strings.xml b/packages/InputDevices/res/values-th/strings.xml index 247312836ae4..3b96226bbe81 100644 --- a/packages/InputDevices/res/values-th/strings.xml +++ b/packages/InputDevices/res/values-th/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"ภาษามองโกเลีย"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"ภาษาจอร์เจีย"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"ไทย (เกษมณี)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"ไทย (ปัตตะโชติ)"</string> </resources> diff --git a/packages/InputDevices/res/values-tl/strings.xml b/packages/InputDevices/res/values-tl/strings.xml index cefa8acbbefd..f0cd0f8ce46d 100644 --- a/packages/InputDevices/res/values-tl/strings.xml +++ b/packages/InputDevices/res/values-tl/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongolian"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-tr/strings.xml b/packages/InputDevices/res/values-tr/strings.xml index ba4703c0a9df..a5c89d746b58 100644 --- a/packages/InputDevices/res/values-tr/strings.xml +++ b/packages/InputDevices/res/values-tr/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Moğolca"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gürcüce"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tayca (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tayca (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml index c74b1c13d5bc..dd3aab8c2aac 100644 --- a/packages/InputDevices/res/values-uk/strings.xml +++ b/packages/InputDevices/res/values-uk/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Монгольська"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Грузинська"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Тайська (кедмані)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Тайська (паттачоте)"</string> </resources> diff --git a/packages/InputDevices/res/values-ur/strings.xml b/packages/InputDevices/res/values-ur/strings.xml index 70ce599df680..008cd103c2da 100644 --- a/packages/InputDevices/res/values-ur/strings.xml +++ b/packages/InputDevices/res/values-ur/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"منگؤلی"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"جارجیائی"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"تھائی (کیڈمینی)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"تھائی (پٹاچوٹے)"</string> </resources> diff --git a/packages/InputDevices/res/values-uz/strings.xml b/packages/InputDevices/res/values-uz/strings.xml index bd6c713f7871..2c1c4b064dcd 100644 --- a/packages/InputDevices/res/values-uz/strings.xml +++ b/packages/InputDevices/res/values-uz/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Mongol"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Gruzin"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tay (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tay (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-vi/strings.xml b/packages/InputDevices/res/values-vi/strings.xml index d665f8e6d31d..b5a0b16befb8 100644 --- a/packages/InputDevices/res/values-vi/strings.xml +++ b/packages/InputDevices/res/values-vi/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Tiếng Mông Cổ"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Tiếng Georgia"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Tiếng Thái (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Tiếng Thái (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-zh-rCN/strings.xml b/packages/InputDevices/res/values-zh-rCN/strings.xml index e6c19b310712..97e75e6c5372 100644 --- a/packages/InputDevices/res/values-zh-rCN/strings.xml +++ b/packages/InputDevices/res/values-zh-rCN/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"蒙古语"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"格鲁吉亚语"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"泰语 (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"泰语 (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-zh-rHK/strings.xml b/packages/InputDevices/res/values-zh-rHK/strings.xml index ee3f598ea2ec..45d4b4fd5b40 100644 --- a/packages/InputDevices/res/values-zh-rHK/strings.xml +++ b/packages/InputDevices/res/values-zh-rHK/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"蒙古文"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"格魯吉亞文"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"泰文 (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"泰文 (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-zh-rTW/strings.xml b/packages/InputDevices/res/values-zh-rTW/strings.xml index b25001bf41f2..f0ea94bfba28 100644 --- a/packages/InputDevices/res/values-zh-rTW/strings.xml +++ b/packages/InputDevices/res/values-zh-rTW/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"蒙古文"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"喬治亞文"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"泰文 (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"泰文 (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/values-zu/strings.xml b/packages/InputDevices/res/values-zu/strings.xml index 8f07a9b6ba03..079b841084c4 100644 --- a/packages/InputDevices/res/values-zu/strings.xml +++ b/packages/InputDevices/res/values-zu/strings.xml @@ -51,6 +51,5 @@ <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"isi-Mongolian"</string> <string name="keyboard_layout_georgian" msgid="4596185456863747454">"Georgian"</string> <string name="keyboard_layout_thai_kedmanee" msgid="6637147314580760938">"Isi-Thai (Kedmanee)"</string> - <!-- no translation found for keyboard_layout_thai_pattachote (2547992342794252205) --> - <skip /> + <string name="keyboard_layout_thai_pattachote" msgid="2547992342794252205">"Isi-Thai (Pattachote)"</string> </resources> diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml index c18d73c60551..d4f8f7de23e7 100644 --- a/packages/InputDevices/res/xml/keyboard_layouts.xml +++ b/packages/InputDevices/res/xml/keyboard_layouts.xml @@ -94,7 +94,7 @@ android:name="keyboard_layout_swiss_german" android:label="@string/keyboard_layout_swiss_german_label" android:keyboardLayout="@raw/keyboard_layout_swiss_german" - android:keyboardLocale="de-Latn-CH|gsw-Latn-CH" + android:keyboardLocale="de-Latn-CH,gsw-Latn-CH" android:keyboardLayoutType="qwertz" /> <keyboard-layout diff --git a/packages/PackageInstaller/res/values-az/strings.xml b/packages/PackageInstaller/res/values-az/strings.xml index 8b4b68d7d963..de59d6028ebc 100644 --- a/packages/PackageInstaller/res/values-az/strings.xml +++ b/packages/PackageInstaller/res/values-az/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Tətbiq datasının <xliff:g id="SIZE">%1$s</xliff:g> hissəsini saxlayın."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Bu tətbiq silinsin?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Bu tətbiqi sistemdən silmək istəyirsiniz? <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> kopya da silinəcək."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Bu tətbiqi şəxsi məkandan silmək istəyirsiniz?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Bu tətbiqi məxfi məkandan silmək istəyirsiniz?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"İşləyən sistemlər silinmələr"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Uğursuz olan sistemlər silinmələr"</string> <string name="uninstalling" msgid="8709566347688966845">"Sistemdən silinir..."</string> diff --git a/packages/PackageInstaller/res/values-be/strings.xml b/packages/PackageInstaller/res/values-be/strings.xml index d1cb7d05e682..a4c309767125 100644 --- a/packages/PackageInstaller/res/values-be/strings.xml +++ b/packages/PackageInstaller/res/values-be/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Захаваць даныя праграмы (<xliff:g id="SIZE">%1$s</xliff:g>)."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Выдаліць гэту праграму?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Выдаліць гэту праграму? Таксама будзе выдалены клон \"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>\"."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Выдаліць гэту праграму з прыватнай аўдыторыі?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Выдаліць гэту праграму з прыватнай прасторы?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Актыўныя выдаленні"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Нявыкананыя выдаленні"</string> <string name="uninstalling" msgid="8709566347688966845">"Ідзе выдаленне…"</string> diff --git a/packages/PackageInstaller/res/values-bn/strings.xml b/packages/PackageInstaller/res/values-bn/strings.xml index 231f451478de..d7ad522d6b33 100644 --- a/packages/PackageInstaller/res/values-bn/strings.xml +++ b/packages/PackageInstaller/res/values-bn/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"অ্যাপ ডেটার মধ্যে <xliff:g id="SIZE">%1$s</xliff:g> রেখে দিন।"</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"আপনি এই অ্যাপ মুছে ফেলতে চান?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"আপনি এই অ্যাপ আনইনস্টল করতে চান? <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ক্লোনও মুছে ফেলা হবে।"</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"আপনার ব্যক্তিগত স্পেস থেকে এই অ্যাপ আনইনস্টল করতে চান?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"আপনার প্রাইভেট স্পেস থেকে এই অ্যাপ আনইনস্টল করতে চান?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"আনইনস্টল করা হচ্ছে"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"আনইনস্টল করা যায়নি"</string> <string name="uninstalling" msgid="8709566347688966845">"আনইনস্টল করা হচ্ছে…"</string> diff --git a/packages/PackageInstaller/res/values-de/strings.xml b/packages/PackageInstaller/res/values-de/strings.xml index ef9d207473c0..21651ba1a42c 100644 --- a/packages/PackageInstaller/res/values-de/strings.xml +++ b/packages/PackageInstaller/res/values-de/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"<xliff:g id="SIZE">%1$s</xliff:g> an App-Daten behalten."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Möchtest du diese App löschen?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Möchtest du diese App deinstallieren? Der <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-Klon wird ebenfalls gelöscht."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Möchtest du diese App in deinem privaten Bereich deinstallieren?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Möchtest du diese App deinstallieren und damit aus deinem vertraulichen Bereich entfernen?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Laufende Deinstallationen"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Fehlgeschlagene Deinstallationen"</string> <string name="uninstalling" msgid="8709566347688966845">"Wird deinstalliert..."</string> diff --git a/packages/PackageInstaller/res/values-fa/strings.xml b/packages/PackageInstaller/res/values-fa/strings.xml index 288653ad5b36..e2ab36fb6fd4 100644 --- a/packages/PackageInstaller/res/values-fa/strings.xml +++ b/packages/PackageInstaller/res/values-fa/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"<xliff:g id="SIZE">%1$s</xliff:g> از دادههای برنامه را نگهدارید."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"میخواهید این برنامه را حذف کنید؟"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"میخواهید این برنامه را حذف نصب کنید؟ همسانه <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> هم حذف خواهد شد."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"میخواهید این برنامه از فضای خصوصی حذف نصب شود؟"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"میخواهید این برنامه را از فضای خصوصی حذف نصب کنید؟"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"حذفنصبهای درحال انجام"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"حذفنصبهای ناموفق"</string> <string name="uninstalling" msgid="8709566347688966845">"درحال حذف نصب..."</string> @@ -122,7 +122,7 @@ <string name="unarchive_action_required_body" msgid="1679431572983989231">"برای بازیابی این برنامه، مراحل بعدی را دنبال کنید"</string> <string name="unarchive_error_installer_disabled_title" msgid="4815715617014985605">"<xliff:g id="INSTALLERNAME">%1$s</xliff:g> غیرفعال است"</string> <string name="unarchive_error_installer_disabled_body" msgid="4820821285907011729">"برای بازیابی این برنامه، <xliff:g id="INSTALLERNAME">%1$s</xliff:g> را در «تنظیمات» فعال کنید"</string> - <string name="unarchive_error_installer_uninstalled_title" msgid="3748354109176326489">"<xliff:g id="INSTALLERNAME">%1$s</xliff:g> حذف نصب شده است"</string> + <string name="unarchive_error_installer_uninstalled_title" msgid="3748354109176326489">"<xliff:g id="INSTALLERNAME">%1$s</xliff:g> حذف نصب شده است"</string> <string name="unarchive_error_installer_uninstalled_body" msgid="944733542444183204">"برای بازیابی این برنامه، باید <xliff:g id="INSTALLERNAME">%1$s</xliff:g> را نصب کنید"</string> <string name="unarchive_action_required_continue" msgid="5711202111224184257">"ادامه دادن"</string> <string name="unarchive_clear_storage_button" msgid="1549537154535608744">"پاک کردن فضای ذخیرهسازی"</string> diff --git a/packages/PackageInstaller/res/values-fr-rCA/strings.xml b/packages/PackageInstaller/res/values-fr-rCA/strings.xml index de38df3ad708..60075e6703fb 100644 --- a/packages/PackageInstaller/res/values-fr-rCA/strings.xml +++ b/packages/PackageInstaller/res/values-fr-rCA/strings.xml @@ -72,13 +72,13 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Garder <xliff:g id="SIZE">%1$s</xliff:g> de données d\'application."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Voulez-vous supprimer cette application?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Voulez-vous désinstaller cette application? Le clone de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> sera aussi supprimé."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Voulez-vous désinstaller cette application de votre Espace privé?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Voulez-vous désinstaller cette appli de votre espace privé?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Désinstallations en cours…"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Désinstallations échouées"</string> <string name="uninstalling" msgid="8709566347688966845">"Désinstallation en cours…"</string> <string name="uninstalling_app" msgid="8866082646836981397">"Désinstallation de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> en cours…"</string> <string name="uninstall_done" msgid="439354138387969269">"Désinstallation terminée."</string> - <string name="uninstall_done_app" msgid="4588850984473605768">"L\'application <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> a bien été désinstallée"</string> + <string name="uninstall_done_app" msgid="4588850984473605768">"L\'appli <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> a bien été désinstallée"</string> <string name="uninstall_done_clone_app" msgid="5578308154544195413">"Le clone de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> a été supprimé"</string> <string name="uninstall_failed" msgid="1847750968168364332">"Échec de la désinstallation."</string> <string name="uninstall_failed_app" msgid="5506028705017601412">"La désinstallation de <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> n\'a pas réussi."</string> diff --git a/packages/PackageInstaller/res/values-hr/strings.xml b/packages/PackageInstaller/res/values-hr/strings.xml index 92ad7f049606..30f3f49b6d5b 100644 --- a/packages/PackageInstaller/res/values-hr/strings.xml +++ b/packages/PackageInstaller/res/values-hr/strings.xml @@ -78,7 +78,7 @@ <string name="uninstalling" msgid="8709566347688966845">"Deinstaliranje…"</string> <string name="uninstalling_app" msgid="8866082646836981397">"Deinstaliranje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>…"</string> <string name="uninstall_done" msgid="439354138387969269">"Deinstalacija je završena."</string> - <string name="uninstall_done_app" msgid="4588850984473605768">"Aplikacija <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> deinstalirana"</string> + <string name="uninstall_done_app" msgid="4588850984473605768">"Aplikacija <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> je deinstalirana"</string> <string name="uninstall_done_clone_app" msgid="5578308154544195413">"Izbrisan je klon paketa <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>"</string> <string name="uninstall_failed" msgid="1847750968168364332">"Deinstalacija nije uspjela."</string> <string name="uninstall_failed_app" msgid="5506028705017601412">"Deinstaliranje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> nije uspjelo."</string> diff --git a/packages/PackageInstaller/res/values-hy/strings.xml b/packages/PackageInstaller/res/values-hy/strings.xml index d9fb570d891e..ed5323fdad72 100644 --- a/packages/PackageInstaller/res/values-hy/strings.xml +++ b/packages/PackageInstaller/res/values-hy/strings.xml @@ -63,7 +63,7 @@ <string name="archive_application_text_all_users" msgid="3151229641681672580">"Արխիվացնե՞լ այս հավելվածը բոլոր օգտատերերի համար։ Ձեր անձնական տվյալները կպահվեն"</string> <string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"Արխիվացնե՞լ այս հավելվածը ձեր աշխատանքային պրոֆիլում։ Ձեր անձնական տվյալները կպահվեն"</string> <string name="archive_application_text_user" msgid="2586558895535581451">"Արխիվացնե՞լ այս հավելվածը <xliff:g id="USERNAME">%1$s</xliff:g> օգտատիրոջ համար։ Ձեր անձնական տվյալները կպահվեն"</string> - <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"Արխիվացնե՞լ այս հավելվածը ձեր անձնական տարածքից։ Ձեր անձնական տվյալները կպահվեն"</string> + <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"Արխիվացնե՞լ այս հավելվածը ձեր մասնավոր տարածքից։ Ձեր անձնական տվյալները կպահվեն"</string> <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Ապատեղադրե՞լ այս հավելվածը "<b>"բոլոր"</b>" օգտատերերի համար: Հավելվածը և դրա տվյալները կհեռացվեն սարքի "<b>"բոլոր"</b>" օգտատերերից:"</string> <string name="uninstall_application_text_user" msgid="498072714173920526">"Ապատեղադրե՞լ այս հավելվածը <xliff:g id="USERNAME">%1$s</xliff:g> օգտատիրոջ համար:"</string> <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Հեռացնե՞լ այս հավելվածը ձեր աշխատանքային պրոֆիլից"</string> @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Չհեռացնել հավելվածների տվյալները (<xliff:g id="SIZE">%1$s</xliff:g>):"</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Ջնջե՞լ այս հավելվածը"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Ապատեղադրե՞լ այս հավելվածը։ <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-ի կլոնը նույնպես կջնջվի։"</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Ապատեղադրե՞լ այս հավելվածը ձեր անձնական տարածքից"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Ապատեղադրե՞լ այս հավելվածը ձեր մասնավոր տարածքից"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Ընթացիկ ապատեղադրումներ"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Ձախողված ապատեղադրումներ"</string> <string name="uninstalling" msgid="8709566347688966845">"Ապատեղադրվում է…"</string> diff --git a/packages/PackageInstaller/res/values-in/strings.xml b/packages/PackageInstaller/res/values-in/strings.xml index 8115b50d2dad..36cb404a2665 100644 --- a/packages/PackageInstaller/res/values-in/strings.xml +++ b/packages/PackageInstaller/res/values-in/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Pertahankan data aplikasi sebesar <xliff:g id="SIZE">%1$s</xliff:g>."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Ingin menghapus aplikasi ini?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Ingin meng-uninstal aplikasi ini? Clone <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> juga akan dihapus."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Ingin meng-uninstal aplikasi ini dari ruang pribadi?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Ingin meng-uninstal aplikasi ini dari ruang privasi?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Menjalankan proses uninstal"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Proses uninstal yang gagal"</string> <string name="uninstalling" msgid="8709566347688966845">"Meng-uninstal..."</string> diff --git a/packages/PackageInstaller/res/values-is/strings.xml b/packages/PackageInstaller/res/values-is/strings.xml index 9d24d1859fb9..80ace2352abd 100644 --- a/packages/PackageInstaller/res/values-is/strings.xml +++ b/packages/PackageInstaller/res/values-is/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Halda <xliff:g id="SIZE">%1$s</xliff:g> af forritagögnum."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Viltu eyða þessu forriti?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Viltu fjarlægja þetta forrit? Afriti af <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> verður einnig eytt."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Viltu fjarlægja þetta forrit úr einkarýminu?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Viltu fjarlægja þetta forrit úr leynirýminu?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Fjarlægingar í gangi"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Fjarlægingar sem mistókust"</string> <string name="uninstalling" msgid="8709566347688966845">"Fjarlægir…"</string> diff --git a/packages/PackageInstaller/res/values-ka/strings.xml b/packages/PackageInstaller/res/values-ka/strings.xml index 29cc33f5101e..594848ad8a10 100644 --- a/packages/PackageInstaller/res/values-ka/strings.xml +++ b/packages/PackageInstaller/res/values-ka/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"შენარჩუნდეს აპების მონაცემების <xliff:g id="SIZE">%1$s</xliff:g>."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"გსურთ ამ აპის წაშლა?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"გსურთ ამ აპის დეინსტალაცია? <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> კლონი ასევე წაიშლება."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"გსურთ ამ აპის დეინსტალაცია თქვენი პირადი სივრციდან?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"გსურთ ამ აპის დეინსტალაცია თქვენი კერძო სივრციდან?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"გაშვებული დეინსტალაციები"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"შეუსრულებელი დეინსტალაციები"</string> <string name="uninstalling" msgid="8709566347688966845">"მიმდინარეობს დეინსტალაცია…"</string> diff --git a/packages/PackageInstaller/res/values-kk/strings.xml b/packages/PackageInstaller/res/values-kk/strings.xml index a13cc4bf6917..182aaa63ae78 100644 --- a/packages/PackageInstaller/res/values-kk/strings.xml +++ b/packages/PackageInstaller/res/values-kk/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Қолданба деректерін (<xliff:g id="SIZE">%1$s</xliff:g>) сақтау."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Осы қолданба жойылсын ба?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Осы қолданба жойылсын ба? <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> клоны да жойылады."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Осы құрылғыны жеке бөлмеңізден жойғыңыз келе ме?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Осы қолданбаны құпия кеңістіктен жою керек пе?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Орындалып жатқан жою процестері"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Сәтсіз жою әрекеттері"</string> <string name="uninstalling" msgid="8709566347688966845">"Жойылуда…"</string> diff --git a/packages/PackageInstaller/res/values-km/strings.xml b/packages/PackageInstaller/res/values-km/strings.xml index 2ec2f4d615f8..ea9a0159e564 100644 --- a/packages/PackageInstaller/res/values-km/strings.xml +++ b/packages/PackageInstaller/res/values-km/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"រក្សាទុកទិន្នន័យកម្មវិធីទំហំ <xliff:g id="SIZE">%1$s</xliff:g>។"</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"តើអ្នកចង់លុបកម្មវិធីនេះដែរឬទេ?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"តើអ្នកចង់លុបកម្មវិធីនេះដែរឬទេ? ក្លូន <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ក៏នឹងត្រូវបានលុបផងដែរ។"</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"តើអ្នកចង់លុបកម្មវិធីនេះចេញពី private space របស់អ្នកដែរទេ?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"តើអ្នកចង់លុបកម្មវិធីនេះចេញពីលំហឯកជនរបស់អ្នកដែរទេ?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"កំពុងដំណើរការការលុប"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"ការលុបដែលបរាជ័យ"</string> <string name="uninstalling" msgid="8709566347688966845">"កំពុងលុប…"</string> diff --git a/packages/PackageInstaller/res/values-mn/strings.xml b/packages/PackageInstaller/res/values-mn/strings.xml index ab2bb9f0472b..3748f178ef66 100644 --- a/packages/PackageInstaller/res/values-mn/strings.xml +++ b/packages/PackageInstaller/res/values-mn/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Аппын өгөгдлийн <xliff:g id="SIZE">%1$s</xliff:g>-г үлдээнэ үү."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Та энэ аппыг устгахыг хүсэж байна уу?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Та энэ аппыг устгахыг хүсэж байна уу? <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>-н хувилалыг мөн устгана."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Та энэ аппыг хувийн орон зайнаасаа устгахдаа итгэлтэй байна уу?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Та энэ аппыг хаалттай орон зайнаасаа устгахдаа итгэлтэй байна уу?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Устгаж байна"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Устгаж чадсангүй"</string> <string name="uninstalling" msgid="8709566347688966845">"Устгаж байна…"</string> diff --git a/packages/PackageInstaller/res/values-ms/strings.xml b/packages/PackageInstaller/res/values-ms/strings.xml index e4d321ca5142..67e10ad44b91 100644 --- a/packages/PackageInstaller/res/values-ms/strings.xml +++ b/packages/PackageInstaller/res/values-ms/strings.xml @@ -63,7 +63,7 @@ <string name="archive_application_text_all_users" msgid="3151229641681672580">"Arkibkan apl ini untuk semua pengguna? Data peribadi anda akan disimpan"</string> <string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"Arkibkan apl ini dalam profil kerja anda? Data peribadi anda akan disimpan"</string> <string name="archive_application_text_user" msgid="2586558895535581451">"Arkibkan apl ini untuk <xliff:g id="USERNAME">%1$s</xliff:g>? Data peribadi anda akan disimpan"</string> - <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"Adakah anda mahu mengarkibkan apl ini daripada ruang peribadi anda? Data peribadi anda akan disimpan"</string> + <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"Adakah anda mahu mengarkibkan apl ini daripada ruang persendirian anda? Data peribadi anda akan disimpan"</string> <string name="uninstall_application_text_all_users" msgid="575491774380227119">"Adakah anda mahu menyahpasang apl ini untuk "<b>"semua"</b>" pengguna? Aplikasi dan datanya akan dialih keluar daripada "<b>"semua"</b>" pengguna pada peranti."</string> <string name="uninstall_application_text_user" msgid="498072714173920526">"Adakah anda ingin menyahpasang apl ini untuk pengguna <xliff:g id="USERNAME">%1$s</xliff:g>?"</string> <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"Adakah anda mahu menyahpasang apl ini daripada profil kerja anda?"</string> @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Simpan <xliff:g id="SIZE">%1$s</xliff:g> data apl."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Adakah anda mahu memadamkan apl ini?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Adakah anda mahu menyahpasang apl ini? Klon <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> juga akan dipadamkan."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Adakah anda mahu menyahpasang apl ini daripada ruang peribadi anda?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Adakah anda mahu menyahpasang apl ini daripada ruang persendirian anda?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Penyahpasangan yang sedang berjalan"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Penyahpasangan yang gagal"</string> <string name="uninstalling" msgid="8709566347688966845">"Menyahpasang…"</string> diff --git a/packages/PackageInstaller/res/values-or/strings.xml b/packages/PackageInstaller/res/values-or/strings.xml index 8bf3225e530b..b99bf1a3c85d 100644 --- a/packages/PackageInstaller/res/values-or/strings.xml +++ b/packages/PackageInstaller/res/values-or/strings.xml @@ -78,7 +78,7 @@ <string name="uninstalling" msgid="8709566347688966845">"ଅନ୍ଇନଷ୍ଟଲ୍ କରାଯାଉଛି…"</string> <string name="uninstalling_app" msgid="8866082646836981397">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ଅନଇନଷ୍ଟଲ୍ କରାଯାଉଛି…"</string> <string name="uninstall_done" msgid="439354138387969269">"ଅନଇନଷ୍ଟଲ୍ ହୋଇଗଲା।"</string> - <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>କୁ ଅନଇନଷ୍ଟଲ୍ କରାଗଲା"</string> + <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>କୁ ଅନଇନଷ୍ଟଲ କରାଯାଇଛି"</string> <string name="uninstall_done_clone_app" msgid="5578308154544195413">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>ର କ୍ଲୋନକୁ ଡିଲିଟ କରାଯାଇଛି"</string> <string name="uninstall_failed" msgid="1847750968168364332">"ଅନଇନଷ୍ଟଲ୍ କରିହେଲା ନାହିଁ।"</string> <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ଅନଇନଷ୍ଟଲ୍ କରିବା ସଫଳ ହେଲାନାହିଁ।"</string> diff --git a/packages/PackageInstaller/res/values-pa/strings.xml b/packages/PackageInstaller/res/values-pa/strings.xml index 34a0945e4aa7..1db92a00683e 100644 --- a/packages/PackageInstaller/res/values-pa/strings.xml +++ b/packages/PackageInstaller/res/values-pa/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"<xliff:g id="SIZE">%1$s</xliff:g> ਐਪ ਡਾਟਾ ਰੱਖੋ।"</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"ਕੀ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਮਿਟਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"ਕੀ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਅਣਸਥਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ਦੇ ਕਲੋਨ ਨੂੰ ਵੀ ਮਿਟਾਇਆ ਜਾਵੇਗਾ।"</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"ਕੀ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਆਪਣੇ ਨਿੱਜੀ ਸਪੇਸ ਤੋਂ ਅਣਸਥਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"ਕੀ ਤੁਸੀਂ ਇਸ ਐਪ ਨੂੰ ਆਪਣੀ ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਤੋਂ ਅਣਸਥਾਪਤ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"ਚੱਲ ਰਹੀਆਂ ਅਣਸਥਾਪਨਾਵਾਂ"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"ਅਸਫਲ ਅਣਸਥਾਪਨਾਵਾਂ"</string> <string name="uninstalling" msgid="8709566347688966845">"ਅਣਸਥਾਪਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string> diff --git a/packages/PackageInstaller/res/values-pl/strings.xml b/packages/PackageInstaller/res/values-pl/strings.xml index 22a78e3b46c6..0f762c7dec18 100644 --- a/packages/PackageInstaller/res/values-pl/strings.xml +++ b/packages/PackageInstaller/res/values-pl/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Zachowaj <xliff:g id="SIZE">%1$s</xliff:g> danych aplikacji."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Chcesz usunąć tę aplikację?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Chcesz odinstalować tę aplikację? Klon aplikacji <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> również zostanie usunięty."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Chcesz odinstalować tę aplikację ze swojego obszaru prywatnego?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Chcesz odinstalować tę aplikację ze swojej przestrzeni prywatnej?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Aktywne odinstalowania"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Nieudane odinstalowania"</string> <string name="uninstalling" msgid="8709566347688966845">"Odinstalowuję…"</string> diff --git a/packages/PackageInstaller/res/values-sl/strings.xml b/packages/PackageInstaller/res/values-sl/strings.xml index 05b826fc2506..69d954d8cda4 100644 --- a/packages/PackageInstaller/res/values-sl/strings.xml +++ b/packages/PackageInstaller/res/values-sl/strings.xml @@ -78,7 +78,7 @@ <string name="uninstalling" msgid="8709566347688966845">"Odstranjevanje …"</string> <string name="uninstalling_app" msgid="8866082646836981397">"Odmeščanje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> …"</string> <string name="uninstall_done" msgid="439354138387969269">"Odstranitev je končana."</string> - <string name="uninstall_done_app" msgid="4588850984473605768">"Aplikacija <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> je bila odstranjena."</string> + <string name="uninstall_done_app" msgid="4588850984473605768">"Aplikacija <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> je bila odmeščena."</string> <string name="uninstall_done_clone_app" msgid="5578308154544195413">"Klonirana aplikacija <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> je izbrisana"</string> <string name="uninstall_failed" msgid="1847750968168364332">"Odstranitev ni uspela."</string> <string name="uninstall_failed_app" msgid="5506028705017601412">"Odmeščanje aplikacije <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ni uspelo."</string> diff --git a/packages/PackageInstaller/res/values-tr/strings.xml b/packages/PackageInstaller/res/values-tr/strings.xml index 0af23520f870..006ad52297de 100644 --- a/packages/PackageInstaller/res/values-tr/strings.xml +++ b/packages/PackageInstaller/res/values-tr/strings.xml @@ -72,7 +72,7 @@ <string name="uninstall_keep_data" msgid="7002379587465487550">"Uygulama verilerinin <xliff:g id="SIZE">%1$s</xliff:g> kadarını sakla."</string> <string name="uninstall_application_text_current_user_clone_profile" msgid="835170400160011636">"Bu uygulamayı silmek istiyor musunuz?"</string> <string name="uninstall_application_text_with_clone_instance" msgid="6944473334273349036">"Bu uygulamanın yüklemesini kaldırmak istiyor musunuz? <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> klonu da silinecektir."</string> - <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Bu uygulamanın gizli alanınızdaki yüklemesini kaldırmak istiyor musunuz?"</string> + <string name="uninstall_application_text_current_user_private_profile" msgid="867004464945674674">"Bu uygulamanın özel alanınızdaki yüklemesini kaldırmak istiyor musunuz?"</string> <string name="uninstalling_notification_channel" msgid="840153394325714653">"Devam eden yükleme kaldırma işlemleri"</string> <string name="uninstall_failure_notification_channel" msgid="1136405866767576588">"Başarısız yükleme kaldırma işlemleri"</string> <string name="uninstalling" msgid="8709566347688966845">"Yükleme kaldırılıyor…"</string> diff --git a/packages/PackageInstaller/res/values-ur/strings.xml b/packages/PackageInstaller/res/values-ur/strings.xml index 444fdd701e17..c73b79ab72e2 100644 --- a/packages/PackageInstaller/res/values-ur/strings.xml +++ b/packages/PackageInstaller/res/values-ur/strings.xml @@ -78,7 +78,7 @@ <string name="uninstalling" msgid="8709566347688966845">"اَن انسٹال ہو رہا ہے…"</string> <string name="uninstalling_app" msgid="8866082646836981397">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> ان انسٹال ہو رہا ہے…"</string> <string name="uninstall_done" msgid="439354138387969269">"اَن انسٹال مکمل ہو گیا۔"</string> - <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> اَن انسٹال ہو گیا"</string> + <string name="uninstall_done_app" msgid="4588850984473605768">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> اَن انسٹال ہو گیا"</string> <string name="uninstall_done_clone_app" msgid="5578308154544195413">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> کا کلون حذف کر دیا گیا ہے"</string> <string name="uninstall_failed" msgid="1847750968168364332">"اَن انسٹال ناکام ہو گیا۔"</string> <string name="uninstall_failed_app" msgid="5506028705017601412">"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g> کو ان انسٹال کرنا ناکام ہو گیا۔"</string> @@ -122,7 +122,7 @@ <string name="unarchive_action_required_body" msgid="1679431572983989231">"اس ایپ کو بحال کرنے کے لیے اگلے مراحل کی پیروی کریں"</string> <string name="unarchive_error_installer_disabled_title" msgid="4815715617014985605">"<xliff:g id="INSTALLERNAME">%1$s</xliff:g> غیر فعال ہے"</string> <string name="unarchive_error_installer_disabled_body" msgid="4820821285907011729">"اس ایپ کو بحال کرنے کے لیے، ترتیبات میں <xliff:g id="INSTALLERNAME">%1$s</xliff:g> کو فعال کریں"</string> - <string name="unarchive_error_installer_uninstalled_title" msgid="3748354109176326489">"<xliff:g id="INSTALLERNAME">%1$s</xliff:g> اَن انسٹال ہو گیا ہے"</string> + <string name="unarchive_error_installer_uninstalled_title" msgid="3748354109176326489">"<xliff:g id="INSTALLERNAME">%1$s</xliff:g> اَن انسٹال ہو گیا ہے"</string> <string name="unarchive_error_installer_uninstalled_body" msgid="944733542444183204">"اس ایپ کو بحال کرنے کے لیے، آپ کو <xliff:g id="INSTALLERNAME">%1$s</xliff:g> انسٹال کرنا ہوگا"</string> <string name="unarchive_action_required_continue" msgid="5711202111224184257">"جاری رکھیں"</string> <string name="unarchive_clear_storage_button" msgid="1549537154535608744">"اسٹوریج صاف کریں"</string> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java index eef21991b845..c96644ca8920 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java @@ -23,23 +23,23 @@ import android.app.DialogFragment; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; -import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; - import androidx.annotation.Nullable; /** * Installation failed: Return status code to the caller or display failure UI to user */ public class InstallFailed extends Activity { + private static final String LOG_TAG = InstallFailed.class.getSimpleName(); - /** Label of the app that failed to install */ + /** + * Label of the app that failed to install + */ private CharSequence mLabel; private AlertDialog mDialog; @@ -80,29 +80,29 @@ public class InstallFailed extends Activity { setFinishOnTouchOutside(true); - int statusCode = getIntent().getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); + Intent intent = getIntent(); + int statusCode = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, + PackageInstaller.STATUS_FAILURE); + boolean returnResult = intent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false); - if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) { - int legacyStatus = getIntent().getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, - PackageManager.INSTALL_FAILED_INTERNAL_ERROR); + if (returnResult) { + int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, + PackageManager.INSTALL_FAILED_INTERNAL_ERROR); // Return result if requested Intent result = new Intent(); result.putExtra(Intent.EXTRA_INSTALL_RESULT, legacyStatus); setResult(Activity.RESULT_FIRST_USER, result); finish(); - } else { - Intent intent = getIntent(); - ApplicationInfo appInfo = intent - .getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO); - Uri packageURI = intent.getData(); + } else if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) { + // statusCode will be STATUS_FAILURE_ABORTED if the update-owner confirmation dialog was + // dismissed by the user. We don't want to show a InstallFailed dialog in this case. + // If the user denies install permission for normal installs, this dialog will never be + // triggered as the status code is returned from PackageInstallerActivity.java // Set header icon and title - PackageUtil.AppSnippet as; - PackageManager pm = getPackageManager(); - as = intent.getParcelableExtra(PackageInstallerActivity.EXTRA_APP_SNIPPET, - PackageUtil.AppSnippet.class); + PackageUtil.AppSnippet as = intent.getParcelableExtra( + PackageInstallerActivity.EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class); // Store label for dialog mLabel = as.label; @@ -127,6 +127,8 @@ public class InstallFailed extends Activity { // Get status messages setExplanationFromErrorCode(statusCode); + } else { + finish(); } } @@ -135,6 +137,7 @@ public class InstallFailed extends Activity { * "manage applications" settings page. */ public static class OutOfSpaceDialog extends DialogFragment { + private InstallFailed mActivity; @Override @@ -147,16 +150,16 @@ public class InstallFailed extends Activity { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new AlertDialog.Builder(mActivity) - .setTitle(R.string.out_of_space_dlg_title) - .setMessage(getString(R.string.out_of_space_dlg_text, mActivity.mLabel)) - .setPositiveButton(R.string.manage_applications, (dialog, which) -> { - // launch manage applications - Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); - startActivity(intent); - mActivity.finish(); - }) - .setNegativeButton(R.string.cancel, (dialog, which) -> mActivity.finish()) - .create(); + .setTitle(R.string.out_of_space_dlg_title) + .setMessage(getString(R.string.out_of_space_dlg_text, mActivity.mLabel)) + .setPositiveButton(R.string.manage_applications, (dialog, which) -> { + // launch manage applications + Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE"); + startActivity(intent); + mActivity.finish(); + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> mActivity.finish()) + .create(); } @Override diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java index 1a6c2bb2ec18..59a511db5b3a 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java @@ -30,6 +30,8 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.util.Log; import android.view.View; import android.widget.Button; @@ -91,8 +93,11 @@ public class InstallInstalling extends Activity { // ContentResolver.SCHEME_FILE // STAGED_SESSION_ID extra contains an ID of a previously staged install session. final File sourceFile = new File(mPackageURI.getPath()); - PackageUtil.AppSnippet as = getIntent() - .getParcelableExtra(EXTRA_APP_SNIPPET, PackageUtil.AppSnippet.class); + + // Dialogs displayed while changing update-owner have a blank icon. To fix this, + // fetch the appSnippet from the source file again + PackageUtil.AppSnippet as = PackageUtil.getAppSnippet(this, appInfo, sourceFile); + getIntent().putExtra(EXTRA_APP_SNIPPET, as); AlertDialog.Builder builder = new AlertDialog.Builder(this); @@ -244,6 +249,14 @@ public class InstallInstalling extends Activity { super.onDestroy(); } + @Override + public void finish() { + if (mDialog != null) { + mDialog.dismiss(); + } + super.finish(); + } + /** * Launch the appropriate finish activity (success or failed) for the installation result. * @@ -299,7 +312,11 @@ public class InstallInstalling extends Activity { PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); try { - session.commit(pendingIntent.getIntentSender()); + // Delay committing the session by 100ms to fix a UI glitch while displaying the + // Update-Owner change dialog on top of the Installing dialog + new Handler(Looper.getMainLooper()).postDelayed(() -> { + session.commit(pendingIntent.getIntentSender()); + }, 100); } catch (Exception e) { Log.e(LOG_TAG, "Cannot install package: ", e); launchFailure(PackageInstaller.STATUS_FAILURE, diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java index cf2f85ed5356..13251d8da109 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java @@ -165,7 +165,9 @@ public class InstallStaging extends Activity { if (mStagingTask != null) { mStagingTask.cancel(true); } - + if (mDialog != null) { + mDialog.dismiss(); + } super.onDestroy(); } diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index a4c6ac7d95c7..3fea5996e3ef 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -193,6 +193,7 @@ public class InstallStart extends Activity { if (isSessionInstall) { nextActivity.setClass(this, PackageInstallerActivity.class); + nextActivity.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); } else { Uri packageUri = intent.getData(); diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index 8bed945af32c..e0398aa49dc9 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -176,11 +176,14 @@ public class PackageInstallerActivity extends Activity { } private CharSequence getExistingUpdateOwnerLabel() { + return getApplicationLabel(getExistingUpdateOwner()); + } + + private String getExistingUpdateOwner() { try { final String packageName = mPkgInfo.packageName; final InstallSourceInfo sourceInfo = mPm.getInstallSourceInfo(packageName); - final String existingUpdateOwner = sourceInfo.getUpdateOwnerPackageName(); - return getApplicationLabel(existingUpdateOwner); + return sourceInfo.getUpdateOwnerPackageName(); } catch (NameNotFoundException e) { return null; } @@ -299,6 +302,18 @@ public class PackageInstallerActivity extends Activity { } private void initiateInstall() { + final String existingUpdateOwner = getExistingUpdateOwner(); + if (mSessionId == SessionInfo.INVALID_ID && + !TextUtils.isEmpty(existingUpdateOwner) && + !TextUtils.equals(existingUpdateOwner, mOriginatingPackage)) { + // Since update ownership is being changed, the system will request another + // user confirmation shortly. Thus, we don't need to ask the user to confirm + // installation here. + startInstall(); + return; + } + + // Proceed with user confirmation as we are not changing the update-owner in this install. String pkgName = mPkgInfo.packageName; // Check if there is already a package on the device with this name // but it has been renamed to something else. @@ -465,10 +480,13 @@ public class PackageInstallerActivity extends Activity { @Override protected void onDestroy() { - super.onDestroy(); while (!mActiveUnknownSourcesListeners.isEmpty()) { unregister(mActiveUnknownSourcesListeners.get(0)); } + if (mDialog != null) { + mDialog.dismiss(); + } + super.onDestroy(); } private void bindUi() { 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 f7752ffad899..2e9b7b421a7b 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt @@ -420,25 +420,51 @@ class InstallRepository(private val context: Context) { * * If AppOP is granted and user action is required to proceed with install * * If AppOp grant is to be requested from the user */ - fun requestUserConfirmation(): InstallStage { + fun requestUserConfirmation(): InstallStage? { return if (isTrustedSource) { if (localLogv) { Log.i(LOG_TAG, "Install allowed") } - // Returns InstallUserActionRequired stage if install details could be successfully - // computed, else it returns InstallAborted. - generateConfirmationSnippet() + maybeDeferUserConfirmation() } else { val unknownSourceStage = handleUnknownSources(appOpRequestInfo) if (unknownSourceStage.stageCode == InstallStage.STAGE_READY) { // Source app already has appOp granted. - generateConfirmationSnippet() + maybeDeferUserConfirmation() } else { unknownSourceStage } } } + /** + * If the update-owner for the incoming app is being changed, defer confirming with the + * user and directly proceed with the install. The system will request another + * user confirmation shortly. + */ + private fun maybeDeferUserConfirmation(): InstallStage? { + // 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 && + !TextUtils.isEmpty(existingUpdateOwner) && + !TextUtils.equals(existingUpdateOwner, callingPackage) + ) { + // Since update ownership is being changed, the system will request another + // user confirmation shortly. Thus, we don't need to ask the user to confirm + // installation here. + initiateInstall() + null + } else { + confirmationSnippet + } + } + private fun generateConfirmationSnippet(): InstallStage { val packageSource: Any? val pendingUserActionReason: Int @@ -639,11 +665,14 @@ class InstallRepository(private val context: Context) { } private fun getExistingUpdateOwnerLabel(pkgInfo: PackageInfo): CharSequence? { + return getApplicationLabel(getExistingUpdateOwner(pkgInfo)) + } + + private fun getExistingUpdateOwner(pkgInfo: PackageInfo): String? { return try { val packageName = pkgInfo.packageName val sourceInfo = packageManager.getInstallSourceInfo(packageName) - val existingUpdateOwner = sourceInfo.updateOwnerPackageName - getApplicationLabel(existingUpdateOwner) + sourceInfo.updateOwnerPackageName } catch (e: PackageManager.NameNotFoundException) { null } @@ -861,7 +890,12 @@ class InstallRepository(private val context: Context) { } _installResult.setValue(InstallSuccess(appSnippet, shouldReturnResult, resultIntent)) } else { - _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message)) + if (statusCode != PackageInstaller.STATUS_FAILURE_ABORTED) { + _installResult.setValue(InstallFailed(appSnippet, statusCode, legacyStatus, message)) + } else { + _installResult.setValue(InstallAborted(ABORT_REASON_INTERNAL_ERROR)) + } + } } @@ -889,8 +923,8 @@ class InstallRepository(private val context: Context) { * When the identity of the install source could not be determined, user can skip checking the * source and directly proceed with the install. */ - fun forcedSkipSourceCheck(): InstallStage { - return generateConfirmationSnippet() + fun forcedSkipSourceCheck(): InstallStage? { + return maybeDeferUserConfirmation() } val stagingProgress: LiveData<Int> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt index 072fb2d34928..388e03f023a1 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/viewmodel/InstallViewModel.kt @@ -22,6 +22,7 @@ import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.distinctUntilChanged import com.android.packageinstaller.v2.model.InstallRepository import com.android.packageinstaller.v2.model.InstallStage import com.android.packageinstaller.v2.model.InstallStaging @@ -37,6 +38,19 @@ class InstallViewModel(application: Application, val repository: InstallReposito val currentInstallStage: MutableLiveData<InstallStage> get() = _currentInstallStage + init { + // Since installing is an async operation, we may get the install result later in time. + // Result of the installation will be set in InstallRepository#installResult. + // As such, currentInstallStage will need to add another MutableLiveData as a data source + _currentInstallStage.addSource( + repository.installResult.distinctUntilChanged() + ) { installStage: InstallStage? -> + if (installStage != null) { + _currentInstallStage.value = installStage + } + } + } + fun preprocessIntent(intent: Intent, callerInfo: InstallRepository.CallerInfo) { val stage = repository.performPreInstallChecks(intent, callerInfo) if (stage.stageCode == InstallStage.STAGE_ABORTED) { @@ -62,12 +76,16 @@ class InstallViewModel(application: Application, val repository: InstallReposito private fun checkIfAllowedAndInitiateInstall() { val stage = repository.requestUserConfirmation() - _currentInstallStage.value = stage + if (stage != null) { + _currentInstallStage.value = stage + } } fun forcedSkipSourceCheck() { val stage = repository.forcedSkipSourceCheck() - _currentInstallStage.value = stage + if (stage != null) { + _currentInstallStage.value = stage + } } fun cleanupInstall() { @@ -80,15 +98,7 @@ class InstallViewModel(application: Application, val repository: InstallReposito } fun initiateInstall() { - // Since installing is an async operation, we will get the install result later in time. - // Result of the installation will be set in InstallRepository#mInstallResult. - // As such, mCurrentInstallStage will need to add another MutableLiveData as a data source repository.initiateInstall() - _currentInstallStage.addSource(repository.installResult) { installStage: InstallStage? -> - if (installStage != null) { - _currentInstallStage.value = installStage - } - } } val stagedSessionId: Int diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING index 4fa882265e53..ad3b44f1bcce 100644 --- a/packages/PrintSpooler/TEST_MAPPING +++ b/packages/PrintSpooler/TEST_MAPPING @@ -8,5 +8,10 @@ } ] } + ], + "postsubmit": [ + { + "name": "PrintSpoolerOutOfProcessTests" + } ] } diff --git a/packages/PrintSpooler/res/values-night/themes.xml b/packages/PrintSpooler/res/values-night/themes.xml index 3cc64a6ef266..76fa7b921e77 100644 --- a/packages/PrintSpooler/res/values-night/themes.xml +++ b/packages/PrintSpooler/res/values-night/themes.xml @@ -24,6 +24,7 @@ <style name="Theme.SelectPrinterActivity" parent="android:style/Theme.DeviceDefault"> <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault"> diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml index bd9602540878..22842f724036 100644 --- a/packages/PrintSpooler/res/values/themes.xml +++ b/packages/PrintSpooler/res/values/themes.xml @@ -24,6 +24,7 @@ parent="android:style/Theme.DeviceDefault.Light"> <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item> <item name="android:windowLightStatusBar">true</item> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault.Light"> diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index d25d5dcaac87..ff09084e24cd 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -785,6 +785,9 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } else { onPrinterUnavailable(printerInfo); } + if (mPrinterRegistry != null) { + mPrinterRegistry.setTrackedPrinter(mCurrentPrinter.getId()); + } mDestinationSpinnerAdapter.ensurePrinterInVisibleAdapterPosition(printerInfo); diff --git a/packages/SettingsLib/DataStore/README.md b/packages/SettingsLib/DataStore/README.md index 30cb9932f104..a762ad3fe199 100644 --- a/packages/SettingsLib/DataStore/README.md +++ b/packages/SettingsLib/DataStore/README.md @@ -1,55 +1,93 @@ # Datastore library -This library aims to manage datastore in a consistent way. +This library provides consistent API for data management (including backup, +restore, and metrics logging) on Android platform. + +Notably, it is designed to be flexible and could be utilized for a wide range of +data store besides the settings preferences. ## Overview -A datastore is required to extend the `BackupRestoreStorage` class and implement -either `Observable` or `KeyedObservable` interface, which enforces: - -- Backup and restore: Datastore should support - [data backup](https://developer.android.com/guide/topics/data/backup) to - preserve user experiences on a new device. -- Observer pattern: The - [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to - monitor data change in the datastore and - - trigger - [BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)) - automatically. - - track data change event to log metrics. - - update internal state and take action. +In the high-level design, a persistent datastore aims to support two key +characteristics: + +- **observable**: triggers backup and metrics logging whenever data is + changed. +- **transferable**: offers users with a seamless experience by backing up and + restoring data on to new devices. + +More specifically, Android framework supports +[data backup](https://developer.android.com/guide/topics/data/backup) to +preserve user experiences on a new device. And the +[observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) allows to +monitor data change. ### Backup and restore -The Android backup framework provides +Currently, the Android backup framework provides [BackupAgentHelper](https://developer.android.com/reference/android/app/backup/BackupAgentHelper) and [BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper) -to back up a datastore. However, there are several caveats when implement -`BackupHelper`: +to facilitate data backup. However, there are several caveats to consider when +implementing `BackupHelper`: -- performBackup: The data is updated incrementally but it is not well +- *performBackup*: The data is updated incrementally but it is not well documented. The `ParcelFileDescriptor` state parameters are normally ignored and data is updated even there is no change. -- restoreEntity: The implementation must take care not to seek or close the - underlying data source, nor read more than size() bytes from the stream when - restore (see +- *restoreEntity*: The implementation must take care not to seek or close the + underlying data source, nor read more than `size()` bytes from the stream + when restore (see [BackupDataInputStream](https://developer.android.com/reference/android/app/backup/BackupDataInputStream)). - It is possible a `BackupHelper` prevents other `BackupHelper`s from - restoring data. -- writeNewStateDescription: Existing implementations rarely notice that this - callback is invoked after all entities are restored, and check if necessary - data are all restored in `restoreEntity` (e.g. + It is possible that a `BackupHelper` interferes with the restore process of + other `BackupHelper`s. +- *writeNewStateDescription*: Existing implementations rarely notice that this + callback is invoked after *all* entities are restored. Instead, they check + if necessary data are all restored in the `restoreEntity` (e.g. [BatteryBackupHelper](https://cs.android.com/android/platform/superproject/main/+/main:packages/apps/Settings/src/com/android/settings/fuelgauge/BatteryBackupHelper.java;l=144;drc=cca804e1ed504e2d477be1e3db00fb881ca32736)), which is not robust sometimes. -This library provides more clear API and offers some improvements: +The datastore library will mitigate these problems by providing alternative +APIs. For instance, library users make use of `InputStream` / `OutputStream` to +back up and restore data directly. + +### Observer pattern + +In the current implementation, the Android backup framework requires a manual +call to +[BackupManager.dataChanged](https://developer.android.com/reference/android/app/backup/BackupManager#dataChanged\(\)). +However, it's often observed that this API call is forgotten when using +`SharedPreferences`. Additionally, there's a common need to log metrics when +data changed. To address these limitations, datastore API employed the observer +pattern. + +### API design and advantages -- The implementation only needs to focus on the `BackupRestoreEntity` - interface. The `InputStream` of restore will ensure bounded data are read, - and close the stream will be no-op. -- The library computes checksum of the backup data automatically, so that - unchanged data will not be sent to Android backup system. +Datastore must extend the `BackupRestoreStorage` class (subclass of +[BackupHelper](https://developer.android.com/reference/android/app/backup/BackupHelper)). +The data in a datastore is group by entity, which is represented by +`BackupRestoreEntity`. Basically, a datastore implementation only needs to focus +on the `BackupRestoreEntity`. + +If the datastore is key-value based (e.g. `SharedPreferences`), implements the +`KeyedObservable` interface to offer fine-grained observer. Otherwise, +implements `Observable`. There are builtin thread-safe implementations of the +two interfaces (`KeyedDataObservable` / `DataObservable`). If it is Kotlin, use +delegation to simplify the code. + +Keep in mind that the implementation should call `KeyedObservable.notifyChange` +/ `Observable.notifyChange` whenever internal data is changed, so that the +registered observer will be notified properly. + +For `SharedPreferences` use case, leverage the `SharedPreferencesStorage` +directly. To back up other file based storage, extend the +`BackupRestoreFileStorage` class. + +Here are some highlights of the library: + +- The restore `InputStream` will ensure bounded data are read, and close the + stream is no-op. That being said, all entities are isolated. +- Data checksum is computed automatically, unchanged data will not be sent to + Android backup system. - Data compression is supported: - ZIP best compression is enabled by default, no extra effort needs to be taken. @@ -67,98 +105,159 @@ This library provides more clear API and offers some improvements: successfully restored in those older versions. This is achieved by extending the `BackupRestoreFileStorage` class, and `BackupRestoreFileArchiver` will treat each file as an entity and do the backup / restore. -- Manual `BackupManager.dataChanged` call is unnecessary now, the library will - do the invocation (see next section). +- Manual `BackupManager.dataChanged` call is unnecessary now, the framework + will invoke the API automatically. -### Observer pattern +## Usages -Manual `BackupManager.dataChanged` call is required by current backup framework. -In practice, it is found that `SharedPreferences` usages foget to invoke the -API. Besides, there are common use cases to log metrics when data is changed. -Consequently, observer pattern is employed to resolve the issues. +This section provides [examples](example/ExampleStorage.kt) of datastore. -If the datastore is key-value based (e.g. `SharedPreferences`), implements the -`KeyedObservable` interface to offer fine-grained observer. Otherwise, -implements `Observable`. The library provides thread-safe implementations -(`KeyedDataObservable` / `DataObservable`), and Kotlin delegation will be -helpful. +Here is a datastore with a string data: -Keep in mind that the implementation should call `KeyedObservable.notifyChange` -/ `Observable.notifyChange` whenever internal data is changed, so that the -registered observer will be notified properly. +```kotlin +class ExampleStorage : ObservableBackupRestoreStorage() { + @Volatile // field is manipulated by multiple threads, synchronization might be needed + var data: String? = null + private set -## Usage and example + @AnyThread + fun setData(data: String?) { + this.data = data + // call notifyChange to trigger backup and metrics logging whenever data is changed + if (data != null) { + notifyChange(ChangeReason.UPDATE) + } else { + notifyChange(ChangeReason.DELETE) + } + } + + override val name: String + get() = "ExampleStorage" + + override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = + listOf(StringEntity("data")) + + override fun enableRestore(): Boolean { + return true // check condition like flag, environment, etc. + } + + override fun enableBackup(backupContext: BackupContext): Boolean { + return true // check condition like flag, environment, etc. + } + + @BinderThread + private inner class StringEntity(override val key: String) : BackupRestoreEntity { + override fun backup(backupContext: BackupContext, outputStream: OutputStream) = + if (data != null) { + outputStream.write(data!!.toByteArray(UTF_8)) + EntityBackupResult.UPDATE + } else { + EntityBackupResult.DELETE // delete existing backup data + } + + override fun restore(restoreContext: RestoreContext, inputStream: InputStream) { + // DO NOT call setData API here, which will trigger notifyChange unexpectedly. + // Under the hood, the datastore library will call notifyChange(ChangeReason.RESTORE) + // later to notify observers. + data = String(inputStream.readBytes(), UTF_8) + // Handle restored data in onRestoreFinished() callback + } + } -For `SharedPreferences` use case, leverage the `SharedPreferencesStorage`. To -back up other file based storage, extend the `BackupRestoreFileStorage` class. + override fun onRestoreFinished() { + // TODO: Update state with the restored data. Use this callback instead of "restore()" in + // case the restore action involves several entities. + // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you + } +} +``` -Here is an example of customized datastore, which has a string to back up: +And this is a datastore with key value data: ```kotlin -class MyDataStore : ObservableBackupRestoreStorage() { - // Another option is make it a StringEntity type and maintain a String field inside StringEntity - @Volatile // backup/restore happens on Binder thread - var data: String? = null - private set - - fun setData(data: String?) { - this.data = data - notifyChange(ChangeReason.UPDATE) +class ExampleKeyValueStorage : + BackupRestoreStorage(), KeyedObservable<String> by KeyedDataObservable() { + // thread safe data structure + private val map = ConcurrentHashMap<String, String>() + + override val name: String + get() = "ExampleKeyValueStorage" + + fun updateData(key: String, value: String?) { + if (value != null) { + map[key] = value + notifyChange(ChangeReason.UPDATE) + } else { + map.remove(key) + notifyChange(ChangeReason.DELETE) } + } - override val name: String - get() = "MyData" - - override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = - listOf(StringEntity("data")) - - private inner class StringEntity(override val key: String) : BackupRestoreEntity { - override fun backup( - backupContext: BackupContext, - outputStream: OutputStream, - ) = - if (data != null) { - outputStream.write(data!!.toByteArray(UTF_8)) - EntityBackupResult.UPDATE - } else { - EntityBackupResult.DELETE - } - - override fun restore(restoreContext: RestoreContext, inputStream: InputStream) { - data = String(inputStream.readAllBytes(), UTF_8) - // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you + override fun createBackupRestoreEntities(): List<BackupRestoreEntity> = + listOf(createMapBackupRestoreEntity()) + + private fun createMapBackupRestoreEntity() = + object : BackupRestoreEntity { + override val key: String + get() = "map" + + override fun backup( + backupContext: BackupContext, + outputStream: OutputStream, + ): EntityBackupResult { + // Use TreeMap to achieve predictable and stable order, so that data will not be + // updated to Android backup backend if there is only order change. + val copy = TreeMap(map) + if (copy.isEmpty()) return EntityBackupResult.DELETE + val dataOutputStream = DataOutputStream(outputStream) + dataOutputStream.writeInt(copy.size) + for ((key, value) in copy) { + dataOutputStream.writeUTF(key) + dataOutputStream.writeUTF(value) } - } + return EntityBackupResult.UPDATE + } - override fun onRestoreFinished() { - // TODO: Update state with the restored data. Use this callback instead "restore()" in case - // the restore action involves several entities. - // NOTE: The library will call notifyChange(ChangeReason.RESTORE) for you + override fun restore(restoreContext: RestoreContext, inputStream: InputStream) { + val dataInputString = DataInputStream(inputStream) + repeat(dataInputString.readInt()) { + val key = dataInputString.readUTF() + val value = dataInputString.readUTF() + map[key] = value + } + } } } ``` -In the application class: +All the datastore should be added in the application class: ```kotlin -class MyApplication : Application() { +class ExampleApplication : Application() { override fun onCreate() { - super.onCreate(); - BackupRestoreStorageManager.getInstance(this).add(MyDataStore()); + super.onCreate() + BackupRestoreStorageManager.getInstance(this) + .add(ExampleStorage(), ExampleKeyValueStorage()) } } ``` -In the custom `BackupAgentHelper` class: +Additionally, inject datastore to the custom `BackupAgentHelper` class: ```kotlin -class MyBackupAgentHelper : BackupAgentHelper() { +class ExampleBackupAgent : BackupAgentHelper() { override fun onCreate() { - BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this); + super.onCreate() + BackupRestoreStorageManager.getInstance(this).addBackupAgentHelpers(this) } override fun onRestoreFinished() { - BackupRestoreStorageManager.getInstance(this).onRestoreFinished(); + BackupRestoreStorageManager.getInstance(this).onRestoreFinished() } } ``` + +## Development + +Please preserve the code coverage ratio during development. The current line +coverage is **100% (444/444)** and branch coverage is **93.6% (176/188)**. diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt index 817ee4c56b19..6720e5c6d714 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreEntity.kt @@ -23,7 +23,11 @@ import java.io.IOException import java.io.InputStream import java.io.OutputStream -/** Entity for back up and restore. */ +/** + * Entity for back up and restore. + * + * Note that backup/restore callback is invoked on the binder thread. + */ interface BackupRestoreEntity { /** * Key of the entity. @@ -45,9 +49,12 @@ interface BackupRestoreEntity { /** * Backs up the entity. * + * Back up data in predictable order (e.g. use `TreeMap` instead of `HashMap`), otherwise data + * will be backed up needlessly. + * * @param backupContext context for backup * @param outputStream output stream to back up data - * @return false if backup file is deleted, otherwise true + * @return backup result */ @BinderThread @Throws(IOException::class) diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt index 935f9ccf6ed9..284c97b5ad6c 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorage.kt @@ -22,6 +22,7 @@ import android.app.backup.BackupDataOutput import android.app.backup.BackupHelper import android.os.ParcelFileDescriptor import android.util.Log +import androidx.annotation.BinderThread import androidx.annotation.VisibleForTesting import androidx.collection.MutableScatterMap import com.google.common.io.ByteStreams @@ -38,16 +39,22 @@ import java.util.zip.CRC32 import java.util.zip.CheckedInputStream import java.util.zip.CheckedOutputStream import java.util.zip.Checksum +import javax.annotation.concurrent.ThreadSafe internal const val LOG_TAG = "BackupRestoreStorage" /** - * Storage with backup and restore support. Subclass must implement either [Observable] or - * [KeyedObservable] interface. + * Storage with backup and restore support. + * + * Subclass MUST + * - implement either [Observable] or [KeyedObservable] interface. + * - be thread safe, backup/restore happens on Binder thread, while general data read/write + * operations occur on other threads. * * The storage is identified by a unique string [name] and data set is split into entities * ([BackupRestoreEntity]). */ +@ThreadSafe abstract class BackupRestoreStorage : BackupHelper { /** * A unique string used to disambiguate the various storages within backup agent. @@ -68,7 +75,7 @@ abstract class BackupRestoreStorage : BackupHelper { @VisibleForTesting internal var entities: List<BackupRestoreEntity>? = null /** Entities to back up and restore. */ - abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> + @BinderThread abstract fun createBackupRestoreEntities(): List<BackupRestoreEntity> /** Default codec used to encode/decode the entity data. */ open fun defaultCodec(): BackupCodec = BackupZipCodec.BEST_COMPRESSION @@ -134,7 +141,11 @@ abstract class BackupRestoreStorage : BackupHelper { Log.i(LOG_TAG, "[$name] Backup end") } - /** Returns if backup is enabled. */ + /** + * Returns if backup is enabled. + * + * If disabled, [performBackup] will be no-op, all entities backup are skipped. + */ open fun enableBackup(backupContext: BackupContext): Boolean = true open fun wrapBackupOutputStream(codec: BackupCodec, outputStream: OutputStream): OutputStream { @@ -172,7 +183,11 @@ abstract class BackupRestoreStorage : BackupHelper { private fun ensureEntities(): List<BackupRestoreEntity> = entities ?: createBackupRestoreEntities().also { entities = it } - /** Returns if restore is enabled. */ + /** + * Returns if restore is enabled. + * + * If disabled, [restoreEntity] will be no-op, all entities restore are skipped. + */ open fun enableRestore(): Boolean = true open fun wrapRestoreInputStream( @@ -188,12 +203,13 @@ abstract class BackupRestoreStorage : BackupHelper { } final override fun writeNewStateDescription(newState: ParcelFileDescriptor) { + if (!enableRestore()) return entities = null // clear to reduce memory footprint newState.writeAndClearEntityStates() onRestoreFinished() } - /** Callbacks when restore finished. */ + /** Callbacks when entity data are all restored. */ open fun onRestoreFinished() {} @VisibleForTesting diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt index 99998ffc13ec..26534baaa47d 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageTest.kt @@ -248,6 +248,15 @@ class BackupRestoreStorageTest { } @Test + fun writeNewStateDescription_restoreDisabled() { + val storage = spy(TestStorage().apply { enabled = false }) + temporaryFolder.newFile().toParcelFileDescriptor(MODE_WRITE_ONLY or MODE_APPEND).use { + storage.writeNewStateDescription(it) + } + verify(storage, never()).onRestoreFinished() + } + + @Test fun backupAndRestore() { val storage = spy(TestStorage(entity1, entity2)) val backupAgentHelper = BackupAgentHelper() diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java index 05507e0ea11d..493818b2e74f 100644 --- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java @@ -80,14 +80,15 @@ public class FooterPreference extends Preference { continue; } final URLSpan urlSpan = (URLSpan) clickable; - if (!urlSpan.getURL().startsWith(INTENT_URL_PREFIX)) { + final String url = urlSpan.getURL(); + if (url == null || !url.startsWith(INTENT_URL_PREFIX)) { continue; } final int start = spannable.getSpanStart(urlSpan); final int end = spannable.getSpanEnd(urlSpan); spannable.removeSpan(urlSpan); try { - final Intent intent = Intent.parseUri(urlSpan.getURL(), Intent.URI_INTENT_SCHEME); + final Intent intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); final ClickableSpan clickableSpan = new ClickableSpan() { @Override @@ -98,7 +99,7 @@ public class FooterPreference extends Preference { }; spannable.setSpan(clickableSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } catch (URISyntaxException e) { - Log.e(TAG, "Invalid URI " + urlSpan.getURL(), e); + Log.e(TAG, "Invalid URI " + url, e); } } title.setText(spannable); diff --git a/packages/SettingsLib/HelpUtils/res/values-fi/strings.xml b/packages/SettingsLib/HelpUtils/res/values-fi/strings.xml index de2b100a82a1..cef5080c3021 100644 --- a/packages/SettingsLib/HelpUtils/res/values-fi/strings.xml +++ b/packages/SettingsLib/HelpUtils/res/values-fi/strings.xml @@ -17,5 +17,5 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="help_feedback_label" msgid="7106780063063027882">"Ohje ja palaute"</string> + <string name="help_feedback_label" msgid="7106780063063027882">"Ohjeet ja palaute"</string> </resources> diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp index 6407810367cf..c3a91a20c339 100644 --- a/packages/SettingsLib/IllustrationPreference/Android.bp +++ b/packages/SettingsLib/IllustrationPreference/Android.bp @@ -21,6 +21,7 @@ android_library { "SettingsLibColor", "androidx.preference_preference", "lottie", + "settingslib_illustrationpreference_flags_lib", ], sdk_version: "system_current", @@ -31,3 +32,24 @@ android_library { "com.android.permission", ], } + +aconfig_declarations { + name: "settingslib_illustrationpreference_flags", + package: "com.android.settingslib.widget.flags", + container: "system", + srcs: [ + "aconfig/illustrationpreference.aconfig", + ], +} + +java_aconfig_library { + name: "settingslib_illustrationpreference_flags_lib", + aconfig_declarations: "settingslib_illustrationpreference_flags", + + min_sdk_version: "30", + + apex_available: [ + "//apex_available:platform", + "com.android.permission", + ], +} diff --git a/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig b/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig new file mode 100644 index 000000000000..e566d89facaf --- /dev/null +++ b/packages/SettingsLib/IllustrationPreference/aconfig/illustrationpreference.aconfig @@ -0,0 +1,12 @@ +package: "com.android.settingslib.widget.flags" +container: "system" + +flag { + name: "auto_hide_empty_lottie_res" + namespace: "android_settings" + description: "Hides IllustrationPreference when Lottie resource is an empty file" + bug: "337873972" + metadata { + purpose: PURPOSE_BUGFIX + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index 815a101957ad..bbf0315aa475 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -39,12 +39,14 @@ import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import androidx.vectordrawable.graphics.drawable.Animatable2Compat; +import com.android.settingslib.widget.flags.Flags; import com.android.settingslib.widget.preference.illustration.R; import com.airbnb.lottie.LottieAnimationView; import com.airbnb.lottie.LottieDrawable; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; /** @@ -142,7 +144,7 @@ public class IllustrationPreference extends Preference { illustrationFrame.setLayoutParams(lp); illustrationView.setCacheComposition(mCacheComposition); - handleImageWithAnimation(illustrationView); + handleImageWithAnimation(illustrationView, illustrationFrame); handleImageFrameMaxHeight(backgroundView, illustrationView); if (mIsAutoScale) { @@ -332,7 +334,8 @@ public class IllustrationPreference extends Preference { } } - private void handleImageWithAnimation(LottieAnimationView illustrationView) { + private void handleImageWithAnimation(LottieAnimationView illustrationView, + ViewGroup container) { if (mImageDrawable != null) { resetAnimations(illustrationView); illustrationView.setImageDrawable(mImageDrawable); @@ -356,6 +359,25 @@ public class IllustrationPreference extends Preference { } if (mImageResId > 0) { + if (Flags.autoHideEmptyLottieRes()) { + // Check if resource is empty + try (InputStream is = illustrationView.getResources() + .openRawResource(mImageResId)) { + int check = is.read(); + // -1 = end of stream. if first read is end of stream, then file is empty + if (check == -1) { + illustrationView.setVisibility(View.GONE); + container.setVisibility(View.GONE); + return; + } + } catch (IOException e) { + Log.w(TAG, "Unable to open Lottie raw resource", e); + } + + illustrationView.setVisibility(View.VISIBLE); + container.setVisibility(View.VISIBLE); + } + resetAnimations(illustrationView); illustrationView.setImageResource(mImageResId); final Drawable drawable = illustrationView.getDrawable(); diff --git a/packages/SettingsLib/ProfileSelector/res/values-az/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-az/strings.xml index 6469f6f746d0..1e89937ffd77 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-az/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-az/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Şəxsi"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"İş"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Şəxsi"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Məxfi"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-de/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-de/strings.xml index bb9a6a919d3a..56f2ce132a07 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-de/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-de/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Privat"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"Dienstlich"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Privat"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Vertraulich"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-in/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-in/strings.xml index 3859f874e212..c0a445111d4b 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-in/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-in/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Pribadi"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"Kerja"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Pribadi"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Privasi"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-ka/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ka/strings.xml index d6c2e6d7815f..9a8677572150 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-ka/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-ka/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"პირადი"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"სამსახური"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"პირადი"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"კერძო"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-ms/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-ms/strings.xml index e1145c3c1d82..2b1f27b91388 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-ms/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-ms/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Peribadi"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"Kerja"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Peribadi"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Persendirian"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml index 025c1be023ef..e1e68c758a15 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-pa/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"ਨਿੱਜੀ"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"ਕਾਰਜ"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"ਨਿੱਜੀ"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"ਪ੍ਰਾਈਵੇਟ"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-uk/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-uk/strings.xml index 76ab328f95c0..ca95cdb72858 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-uk/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-uk/strings.xml @@ -17,7 +17,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="settingslib_category_personal" msgid="1142302328104700620">"Особисті"</string> - <string name="settingslib_category_work" msgid="4867750733682444676">"Робочі"</string> + <string name="settingslib_category_personal" msgid="1142302328104700620">"Особистий профіль"</string> + <string name="settingslib_category_work" msgid="4867750733682444676">"Робочий профіль"</string> <string name="settingslib_category_private" msgid="5039276873477591386">"Приватний профіль"</string> </resources> diff --git a/packages/SettingsLib/ProfileSelector/res/values-uz/strings.xml b/packages/SettingsLib/ProfileSelector/res/values-uz/strings.xml index 50ccf1de148f..6618edd0fab4 100644 --- a/packages/SettingsLib/ProfileSelector/res/values-uz/strings.xml +++ b/packages/SettingsLib/ProfileSelector/res/values-uz/strings.xml @@ -19,5 +19,5 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="settingslib_category_personal" msgid="1142302328104700620">"Shaxsiy"</string> <string name="settingslib_category_work" msgid="4867750733682444676">"Ish"</string> - <string name="settingslib_category_private" msgid="5039276873477591386">"Yopiq"</string> + <string name="settingslib_category_private" msgid="5039276873477591386">"Maxfiy"</string> </resources> diff --git a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml index 4ced9f2469ab..cece9665b729 100644 --- a/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml +++ b/packages/SettingsLib/SettingsTheme/res/color-v35/settingslib_preference_bg_color.xml @@ -16,8 +16,8 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true" android:color="@color/settingslib_materialColorSecondaryContainer"/> - <item android:state_selected="true" android:color="@color/settingslib_materialColorSecondaryContainer"/> - <item android:state_activated="true" android:color="@color/settingslib_materialColorSecondaryContainer"/> - <item android:color="@color/settingslib_materialColorSurfaceContainerHighest"/> <!-- not selected --> + <item android:state_pressed="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/> + <item android:state_selected="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/> + <item android:state_activated="true" android:color="@color/settingslib_materialColorSurfaceContainerHigh"/> + <item android:color="@color/settingslib_materialColorSurfaceBright"/> <!-- not selected --> </selector>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml index 285ab7301162..eba9c2ceba70 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background.xml @@ -19,12 +19,12 @@ <item android:left="?android:attr/listPreferredItemPaddingStart" android:right="?android:attr/listPreferredItemPaddingEnd" - android:top="1dp"> + android:top="2dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_preference_bg_color" /> <corners - android:radius="?android:attr/dialogCornerRadius" /> + android:radius="@dimen/settingslib_preference_corner_radius" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml index e417307edc3d..5c60f37a7244 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom.xml @@ -19,15 +19,15 @@ <item android:left="?android:attr/listPreferredItemPaddingStart" android:right="?android:attr/listPreferredItemPaddingEnd" - android:top="1dp"> + android:top="2dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_preference_bg_color" /> <corners - android:topLeftRadius="0dp" - android:bottomLeftRadius="?android:attr/dialogCornerRadius" - android:topRightRadius="0dp" - android:bottomRightRadius="?android:attr/dialogCornerRadius" /> + android:topLeftRadius="4dp" + android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius" + android:topRightRadius="4dp" + android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml new file mode 100644 index 000000000000..de64efd23a0d --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_bottom_selected.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="?android:attr/listPreferredItemPaddingStart" + android:right="?android:attr/listPreferredItemPaddingEnd" + android:top="2dp"> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSurfaceContainerHigh" /> + <corners + android:topLeftRadius="4dp" + android:bottomLeftRadius="@dimen/settingslib_preference_corner_radius" + android:topRightRadius="4dp" + android:bottomRightRadius="@dimen/settingslib_preference_corner_radius" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml index e9646575663d..dd70f4f7a146 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center.xml @@ -19,12 +19,12 @@ <item android:left="?android:attr/listPreferredItemPaddingStart" android:right="?android:attr/listPreferredItemPaddingEnd" - android:top="1dp"> + android:top="2dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_preference_bg_color" /> <corners - android:radius="1dp" /> + android:radius="4dp" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml new file mode 100644 index 000000000000..fffc6c8c4bef --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_center_selected.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="?android:attr/listPreferredItemPaddingStart" + android:right="?android:attr/listPreferredItemPaddingEnd" + android:top="2dp"> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSurfaceContainerHigh" /> + <corners + android:radius="4dp" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml new file mode 100644 index 000000000000..f83e3b177200 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_selected.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="?android:attr/listPreferredItemPaddingStart" + android:right="?android:attr/listPreferredItemPaddingEnd" + android:top="2dp"> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSurfaceContainerHigh" /> + <corners + android:radius="@dimen/settingslib_preference_corner_radius" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml index a9d69c264a2c..ab79d18a1ab3 100644 --- a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top.xml @@ -19,15 +19,15 @@ <item android:left="?android:attr/listPreferredItemPaddingStart" android:right="?android:attr/listPreferredItemPaddingEnd" - android:top="1dp"> + android:top="2dp"> <shape android:shape="rectangle"> <solid android:color="@color/settingslib_preference_bg_color" /> <corners - android:topLeftRadius="?android:attr/dialogCornerRadius" - android:bottomLeftRadius="0dp" - android:topRightRadius="?android:attr/dialogCornerRadius" - android:bottomRightRadius="0dp" /> + android:topLeftRadius="@dimen/settingslib_preference_corner_radius" + android:bottomLeftRadius="4dp" + android:topRightRadius="@dimen/settingslib_preference_corner_radius" + android:bottomRightRadius="4dp" /> </shape> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml new file mode 100644 index 000000000000..112ec735aa27 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_round_background_top_selected.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="?android:attr/listPreferredItemPaddingStart" + android:right="?android:attr/listPreferredItemPaddingEnd" + android:top="2dp"> + <shape android:shape="rectangle"> + <solid + android:color="@color/settingslib_materialColorSurfaceContainerHigh" /> + <corners + android:topLeftRadius="@dimen/settingslib_preference_corner_radius" + android:bottomLeftRadius="4dp" + android:topRightRadius="@dimen/settingslib_preference_corner_radius" + android:bottomRightRadius="4dp" /> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml new file mode 100644 index 000000000000..eda7daaf7072 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_preference_category_no_title.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:baselineAligned="false" + android:layout_marginTop="16dp"> +</LinearLayout> diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml index 7c76ea1ad3b7..94ff02d898db 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v35/colors.xml @@ -37,8 +37,11 @@ <!-- Material next track off color--> <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color> + <!-- Dialog text button color. --> + <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color> + <!-- Dialog background color. --> - <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color> + <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</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..8b9501608000 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/colors.xml @@ -37,8 +37,11 @@ <!-- Material next track off color--> <color name="settingslib_track_off_color">@color/settingslib_materialColorSurfaceContainerHighest</color> + <!-- Dialog text button color. --> + <color name="settingslib_dialog_accent">@color/settingslib_materialColorPrimary</color> + <!-- Dialog background color. --> - <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceInverse</color> + <color name="settingslib_dialog_background">@color/settingslib_materialColorSurfaceContainerHigh</color> <!-- Material next track outline color--> <color name="settingslib_track_online_color">@color/settingslib_switch_track_outline_color</color> diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml new file mode 100644 index 000000000000..d783956ee240 --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/dimens.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2024 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<resources> + <dimen name="settingslib_preference_corner_radius">20dp</dimen> +</resources>
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml index cdd5c2500693..6052be3b52e2 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-v35/themes.xml @@ -20,7 +20,7 @@ <item name="android:colorAccent">@color/settingslib_materialColorPrimary</item> <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainer</item> <item name="android:textColorPrimary">@color/settingslib_materialColorOnSurface</item> - <item name="android:textColorSecondary">@color/settingslib_materialColorOnSurfaceVariant</item> + <item name="android:textColorSecondary">@color/settingslib_text_color_secondary</item> <item name="android:textColorTertiary">@color/settingslib_materialColorOutline</item> </style> diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 4147813bd059..45667f55882d 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 { @@ -41,7 +41,7 @@ subprojects { defaultConfig { minSdk = 21 - targetSdk = 34 + targetSdk = 35 } } 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/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt index da1ee77bcbfb..e867a8f0a8d1 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.annotation.VisibleForTesting import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -30,7 +31,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier -import androidx.core.view.WindowCompat import androidx.navigation.NavBackStackEntry import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraphBuilder @@ -82,7 +82,7 @@ open class BrowseActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.Theme_SpaLib) super.onCreate(savedInstanceState) - WindowCompat.setDecorFitsSystemWindows(window, false) + enableEdgeToEdge() spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK) setContent { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt index 52c489324699..d6704a534c3a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/MaterialColors.kt @@ -47,3 +47,7 @@ val ColorScheme.divider: Color val ColorScheme.surfaceTone: Color get() = primary.copy(SettingsOpacity.SurfaceTone) + +/** The overall background color in Settings. */ +val ColorScheme.settingsBackground: Color + get() = surfaceContainer diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt deleted file mode 100644 index 69aba71413f9..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsColors.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.spa.framework.theme - -import android.content.Context -import android.os.Build -import androidx.annotation.VisibleForTesting -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext - -data class SettingsColorScheme( - val background: Color = Color.Unspecified, - val categoryTitle: Color = Color.Unspecified, - val surface: Color = Color.Unspecified, - val surfaceHeader: Color = Color.Unspecified, - val secondaryText: Color = Color.Unspecified, - val primaryContainer: Color = Color.Unspecified, - val onPrimaryContainer: Color = Color.Unspecified, -) - -internal val LocalColorScheme = staticCompositionLocalOf { SettingsColorScheme() } - -@Composable -internal fun settingsColorScheme(isDarkTheme: Boolean): SettingsColorScheme { - val context = LocalContext.current - return remember(isDarkTheme) { - when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - if (isDarkTheme) dynamicDarkColorScheme(context) - else dynamicLightColorScheme(context) - } - isDarkTheme -> darkColorScheme() - else -> lightColorScheme() - } - } -} - -/** - * Creates a light dynamic color scheme. - * - * Use this function to create a color scheme based off the system wallpaper. If the developer - * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a - * light theme variant. - * - * @param context The context required to get system resource data. - */ -@VisibleForTesting -internal fun dynamicLightColorScheme(context: Context): SettingsColorScheme { - val tonalPalette = dynamicTonalPalette(context) - return SettingsColorScheme( - background = tonalPalette.neutral95, - categoryTitle = tonalPalette.primary40, - surface = tonalPalette.neutral99, - surfaceHeader = tonalPalette.neutral90, - secondaryText = tonalPalette.neutralVariant30, - primaryContainer = tonalPalette.primary90, - onPrimaryContainer = tonalPalette.neutral10, - ) -} - -/** - * Creates a dark dynamic color scheme. - * - * Use this function to create a color scheme based off the system wallpaper. If the developer - * changes the wallpaper this color scheme will change accordingly. This dynamic scheme is a dark - * theme variant. - * - * @param context The context required to get system resource data. - */ -@VisibleForTesting -internal fun dynamicDarkColorScheme(context: Context): SettingsColorScheme { - val tonalPalette = dynamicTonalPalette(context) - return SettingsColorScheme( - background = tonalPalette.neutral10, - categoryTitle = tonalPalette.primary90, - surface = tonalPalette.neutral20, - surfaceHeader = tonalPalette.neutral30, - secondaryText = tonalPalette.neutralVariant80, - primaryContainer = tonalPalette.secondary90, - onPrimaryContainer = tonalPalette.neutral10, - ) -} - -@VisibleForTesting -internal fun darkColorScheme(): SettingsColorScheme { - val tonalPalette = tonalPalette() - return SettingsColorScheme( - background = tonalPalette.neutral10, - categoryTitle = tonalPalette.primary90, - surface = tonalPalette.neutral20, - surfaceHeader = tonalPalette.neutral30, - secondaryText = tonalPalette.neutralVariant80, - primaryContainer = tonalPalette.secondary90, - onPrimaryContainer = tonalPalette.neutral10, - ) -} - -@VisibleForTesting -internal fun lightColorScheme(): SettingsColorScheme { - val tonalPalette = tonalPalette() - return SettingsColorScheme( - background = tonalPalette.neutral95, - categoryTitle = tonalPalette.primary40, - surface = tonalPalette.neutral99, - surfaceHeader = tonalPalette.neutral90, - secondaryText = tonalPalette.neutralVariant30, - primaryContainer = tonalPalette.primary90, - onPrimaryContainer = tonalPalette.neutral10, - ) -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt index c395558b769c..d9f82e8c6986 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt @@ -21,7 +21,6 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.ReadOnlyComposable /** * The Material 3 Theme for Settings. @@ -29,24 +28,15 @@ import androidx.compose.runtime.ReadOnlyComposable @Composable fun SettingsTheme(content: @Composable () -> Unit) { val isDarkTheme = isSystemInDarkTheme() - val settingsColorScheme = settingsColorScheme(isDarkTheme) - val colorScheme = materialColorScheme(isDarkTheme).copy( - background = settingsColorScheme.background, - ) - MaterialTheme(colorScheme = colorScheme, typography = rememberSettingsTypography()) { + MaterialTheme( + colorScheme = materialColorScheme(isDarkTheme), + typography = rememberSettingsTypography(), + ) { CompositionLocalProvider( - LocalColorScheme provides settingsColorScheme(isDarkTheme), LocalContentColor provides MaterialTheme.colorScheme.onSurface, ) { content() } } } - -object SettingsTheme { - val colorScheme: SettingsColorScheme - @Composable - @ReadOnlyComposable - get() = LocalColorScheme.current -} 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/button/ActionButtons.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt index 979cf3bddae6..70d353da496c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/button/ActionButtons.kt @@ -88,9 +88,9 @@ private fun RowScope.ActionButton(actionButton: ActionButton) { interactionSource = remember(actionButton) { MutableInteractionSource() }, shape = RectangleShape, colors = ButtonDefaults.filledTonalButtonColors( - containerColor = SettingsTheme.colorScheme.surface, - contentColor = SettingsTheme.colorScheme.categoryTitle, - disabledContainerColor = SettingsTheme.colorScheme.surface, + containerColor = MaterialTheme.colorScheme.surface, + contentColor = MaterialTheme.colorScheme.primary, + disabledContainerColor = MaterialTheme.colorScheme.surface, ), contentPadding = PaddingValues(horizontal = 4.dp, vertical = 20.dp), ) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt index d08d97eb89db..0546719eb8cd 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt @@ -83,7 +83,7 @@ fun SettingsCardContent( Card( shape = CornerExtraSmall, colors = CardDefaults.cardColors( - containerColor = containerColor.takeOrElse { SettingsTheme.colorScheme.surface }, + containerColor = containerColor.takeOrElse { MaterialTheme.colorScheme.surface }, ), modifier = Modifier .fillMaxWidth() diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 56534f41c3df..9a344c3d7f14 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -35,6 +35,7 @@ import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalContentColor @@ -42,11 +43,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle import androidx.compose.material3.Surface import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableFloatStateOf @@ -74,12 +75,17 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import com.android.settingslib.spa.framework.theme.SettingsDimension -import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.settingsBackground import kotlin.math.abs import kotlin.math.max import kotlin.math.roundToInt -@OptIn(ExperimentalMaterial3Api::class) +private val windowInsets: WindowInsets + @Composable + @NonRestartableComposable + get() = WindowInsets.safeDrawing + .only(WindowInsetsSides.Horizontal + WindowInsetsSides.Top) + @Composable internal fun CustomizedTopAppBar( title: @Composable () -> Unit, @@ -91,7 +97,7 @@ internal fun CustomizedTopAppBar( titleTextStyle = MaterialTheme.typography.titleMedium, navigationIcon = navigationIcon, actions = actions, - windowInsets = TopAppBarDefaults.windowInsets, + windowInsets = windowInsets, colors = topAppBarColors(), ) } @@ -118,7 +124,7 @@ internal fun CustomizedLargeTopAppBar( navigationIcon = navigationIcon, actions = actions, colors = topAppBarColors(), - windowInsets = TopAppBarDefaults.windowInsets, + windowInsets = windowInsets, pinnedHeight = ContainerHeight, scrollBehavior = scrollBehavior, ) @@ -140,8 +146,8 @@ private fun Title(title: String, maxLines: Int = Int.MAX_VALUE) { @Composable private fun topAppBarColors() = TopAppBarColors( - containerColor = MaterialTheme.colorScheme.background, - scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader, + containerColor = MaterialTheme.colorScheme.settingsBackground, + scrolledContainerColor = MaterialTheme.colorScheme.surfaceVariant, navigationIconContentColor = MaterialTheme.colorScheme.onSurface, titleContentColor = MaterialTheme.colorScheme.onSurface, actionIconContentColor = MaterialTheme.colorScheme.onSurfaceVariant, @@ -336,7 +342,7 @@ private fun TwoRowsTopAppBar( Modifier.draggable( orientation = Orientation.Vertical, state = rememberDraggableState { delta -> - scrollBehavior.state.heightOffset = scrollBehavior.state.heightOffset + delta + scrollBehavior.state.heightOffset += delta }, onDragStopped = { velocity -> settleAppBar( @@ -411,6 +417,7 @@ private fun TwoRowsTopAppBar( * (leading icon), a title (header), and action icons (trailing icons). Note that the navigation and * the actions are optional. * + * @param modifier a [Modifier] * @param heightPx the total height this layout is capped to * @param navigationIconContentColor the content color that will be applied via a * [LocalContentColor] when composing the navigation icon diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt index 711c8a753532..feb9737bbf39 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt @@ -28,13 +28,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.settingsBackground @Composable fun HomeScaffold(title: String, content: @Composable () -> Unit) { Column( Modifier .fillMaxSize() - .background(color = MaterialTheme.colorScheme.background) + .background(color = MaterialTheme.colorScheme.settingsBackground) .systemBarsPadding() .verticalScroll(rememberScrollState()), ) { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt index c87178db8ffa..4a7937a3c2ac 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt @@ -22,9 +22,11 @@ import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.ExperimentalMaterial3Api @@ -59,6 +61,7 @@ import com.android.settingslib.spa.framework.compose.hideKeyboardAction import com.android.settingslib.spa.framework.compose.horizontalValues import com.android.settingslib.spa.framework.theme.SettingsOpacity import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.settingsBackground import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel @@ -90,6 +93,8 @@ fun SearchScaffold( onSearchQueryChange = { viewModel.searchQuery = it }, ) }, + containerColor = MaterialTheme.colorScheme.settingsBackground, + contentWindowInsets = WindowInsets.safeDrawing, ) { paddingValues -> Box( Modifier diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt index 89194021ecd5..4cf741e517be 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt @@ -23,8 +23,11 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -36,6 +39,7 @@ import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.compose.horizontalValues import com.android.settingslib.spa.framework.compose.verticalValues import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.settingsBackground import com.android.settingslib.spa.widget.preference.Preference import com.android.settingslib.spa.widget.preference.PreferenceModel @@ -54,6 +58,8 @@ fun SettingsScaffold( Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { SettingsTopAppBar(title, scrollBehavior, actions) }, + containerColor = MaterialTheme.colorScheme.settingsBackground, + contentWindowInsets = WindowInsets.safeDrawing, ) { paddingValues -> Box(Modifier.padding(paddingValues.horizontalValues())) { content(paddingValues.verticalValues()) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt index 6f2c38caa3bc..60814bf8cc25 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt @@ -51,8 +51,8 @@ internal fun SettingsTab( .clip(SettingsShape.CornerMedium) .background( color = lerp( - start = SettingsTheme.colorScheme.primaryContainer, - stop = SettingsTheme.colorScheme.surface, + start = MaterialTheme.colorScheme.primaryContainer, + stop = MaterialTheme.colorScheme.surface, fraction = colorFraction, ), ), @@ -61,8 +61,8 @@ internal fun SettingsTab( text = title, style = MaterialTheme.typography.labelLarge, color = lerp( - start = SettingsTheme.colorScheme.onPrimaryContainer, - stop = SettingsTheme.colorScheme.secondaryText, + start = MaterialTheme.colorScheme.onPrimaryContainer, + stop = MaterialTheme.colorScheme.onSurface, fraction = colorFraction, ), ) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt index e39b17597406..4726dadc3688 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt @@ -21,7 +21,9 @@ import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.layout.size import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -37,6 +39,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.settingsBackground import com.android.settingslib.spa.framework.theme.toMediumWeight data class BottomAppBarButton( @@ -54,7 +57,10 @@ fun SuwScaffold( content: @Composable () -> Unit, ) { ActivityTitle(title) - Scaffold { innerPadding -> + Scaffold( + containerColor = MaterialTheme.colorScheme.settingsBackground, + contentWindowInsets = WindowInsets.safeDrawing, + ) { innerPadding -> BoxWithConstraints( Modifier .padding(innerPadding) 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/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt index 6aac5bf3839a..48cd145da124 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt @@ -46,7 +46,7 @@ fun CategoryTitle(title: String) { end = SettingsDimension.itemPaddingEnd, bottom = 8.dp, ), - color = SettingsTheme.colorScheme.categoryTitle, + color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.labelMedium, ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt index 930d0a1872ab..99b2524f0f9e 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/CopyableBody.kt @@ -37,7 +37,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.DpOffset import com.android.settingslib.spa.framework.theme.SettingsDimension -import com.android.settingslib.spa.framework.theme.SettingsTheme @Composable fun CopyableBody(body: String) { @@ -78,7 +77,7 @@ private fun DropdownMenuTitle(text: String) { top = SettingsDimension.itemPaddingAround, bottom = SettingsDimension.buttonPaddingVertical, ), - color = SettingsTheme.colorScheme.categoryTitle, + color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.labelMedium, ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt index d423d9fe5897..6e5f32ebe545 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Text.kt @@ -47,7 +47,6 @@ fun SettingsTitle( modifier = Modifier .padding(vertical = SettingsDimension.paddingTiny) .contentDescription(contentDescription), - color = MaterialTheme.colorScheme.onSurface, style = MaterialTheme.typography.titleMedium.withWeight(useMediumWeight), ) } diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt deleted file mode 100644 index 625663dc9f9a..000000000000 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsColorsTest.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settingslib.spa.framework.theme - -import android.content.Context -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.google.common.truth.Truth.assertThat -import org.junit.Test -import org.junit.runner.RunWith -import androidx.compose.ui.graphics.Color - -@RunWith(AndroidJUnit4::class) -class SettingsColorsTest { - private val context: Context = ApplicationProvider.getApplicationContext() - - @Test - fun testDynamicTheme() { - // The dynamic color could be different in different device, just check basic restrictions: - // 1. text color is different with background color - // 2. primary / spinner color is different with its on-item color - val ls = dynamicLightColorScheme(context) - assertThat(ls.categoryTitle).isNotEqualTo(ls.background) - assertThat(ls.secondaryText).isNotEqualTo(ls.background) - assertThat(ls.primaryContainer).isNotEqualTo(ls.onPrimaryContainer) - - val ds = dynamicDarkColorScheme(context) - assertThat(ds.categoryTitle).isNotEqualTo(ds.background) - assertThat(ds.secondaryText).isNotEqualTo(ds.background) - assertThat(ds.primaryContainer).isNotEqualTo(ds.onPrimaryContainer) - } - - @Test - fun testStaticTheme() { - val ls = lightColorScheme() - assertThat(ls.background).isEqualTo(Color(red = 244, green = 239, blue = 244)) - assertThat(ls.categoryTitle).isEqualTo(Color(red = 103, green = 80, blue = 164)) - assertThat(ls.surface).isEqualTo(Color(red = 255, green = 251, blue = 254)) - assertThat(ls.surfaceHeader).isEqualTo(Color(red = 230, green = 225, blue = 229)) - assertThat(ls.secondaryText).isEqualTo(Color(red = 73, green = 69, blue = 79)) - assertThat(ls.primaryContainer).isEqualTo(Color(red = 234, green = 221, blue = 255)) - assertThat(ls.onPrimaryContainer).isEqualTo(Color(red = 28, green = 27, blue = 31)) - - val ds = darkColorScheme() - assertThat(ds.background).isEqualTo(Color(red = 28, green = 27, blue = 31)) - assertThat(ds.categoryTitle).isEqualTo(Color(red = 234, green = 221, blue = 255)) - assertThat(ds.surface).isEqualTo(Color(red = 49, green = 48, blue = 51)) - assertThat(ds.surfaceHeader).isEqualTo(Color(red = 72, green = 70, blue = 73)) - assertThat(ds.secondaryText).isEqualTo(Color(red = 202, green = 196, blue = 208)) - assertThat(ds.primaryContainer).isEqualTo(Color(red = 232, green = 222, blue = 248)) - assertThat(ds.onPrimaryContainer).isEqualTo(Color(red = 28, green = 27, blue = 31)) - } -} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt index bd8a54bfa4a3..ed7735e38d8a 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt @@ -26,42 +26,35 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.text.font.FontFamily import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.junit.MockitoJUnit -import org.mockito.junit.MockitoRule import org.mockito.kotlin.any -import org.mockito.kotlin.whenever +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.stub @RunWith(AndroidJUnit4::class) class SettingsThemeTest { - @get:Rule - val mockito: MockitoRule = MockitoJUnit.rule() @get:Rule val composeTestRule = createComposeRule() - @Mock - private lateinit var context: Context + private val resources = mock<Resources> { + on { getString(any()) } doReturn "" + } - @Mock - private lateinit var resources: Resources + private val context = mock<Context> { + on { resources } doReturn resources + } private var nextMockResId = 1 - @Before - fun setUp() { - whenever(context.resources).thenReturn(resources) - whenever(resources.getString(any())).thenReturn("") - } - private fun mockAndroidConfig(configName: String, configValue: String) { - whenever(resources.getIdentifier(configName, "string", "android")) - .thenReturn(nextMockResId) - whenever(resources.getString(nextMockResId)).thenReturn(configValue) + resources.stub { + on { getIdentifier(configName, "string", "android") } doReturn nextMockResId + on { getString(nextMockResId) } doReturn configValue + } nextMockResId++ } 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/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt index 6e9bde45e061..8276e18898c6 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt @@ -29,36 +29,46 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map interface IAppOpsController { - val mode: Flow<Int> + val modeFlow: Flow<Int> val isAllowed: Flow<Boolean> - get() = mode.map { it == MODE_ALLOWED } + get() = modeFlow.map { it == MODE_ALLOWED } fun setAllowed(allowed: Boolean) @Mode fun getMode(): Int } +data class AppOps( + val op: Int, + val modeForNotAllowed: Int = MODE_ERRORED, + + /** + * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed. + * + * Security or privacy related app-ops should be set with setUidMode() instead of setMode(). + */ + val setModeByUid: Boolean = false, +) + class AppOpsController( context: Context, private val app: ApplicationInfo, - private val op: Int, - private val modeForNotAllowed: Int = MODE_ERRORED, - private val setModeByUid: Boolean = false, + private val appOps: AppOps, ) : IAppOpsController { private val appOpsManager = context.appOpsManager private val packageManager = context.packageManager - override val mode = appOpsManager.opModeFlow(op, app) + override val modeFlow = appOpsManager.opModeFlow(appOps.op, app) override fun setAllowed(allowed: Boolean) { - val mode = if (allowed) MODE_ALLOWED else modeForNotAllowed + val mode = if (allowed) MODE_ALLOWED else appOps.modeForNotAllowed - if (setModeByUid) { - appOpsManager.setUidMode(op, app.uid, mode) + if (appOps.setModeByUid) { + appOpsManager.setUidMode(appOps.op, app.uid, mode) } else { - appOpsManager.setMode(op, app.uid, app.packageName, mode) + appOpsManager.setMode(appOps.op, app.uid, app.packageName, mode) } - val permission = AppOpsManager.opToPermission(op) + val permission = AppOpsManager.opToPermission(appOps.op) if (permission != null) { packageManager.updatePermissionFlags(permission, app.packageName, PackageManager.FLAG_PERMISSION_USER_SET, @@ -67,5 +77,6 @@ class AppOpsController( } } - @Mode override fun getMode(): Int = appOpsManager.getOpMode(op, app) + @Mode + override fun getMode(): Int = appOpsManager.getOpMode(appOps.op, app) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt new file mode 100644 index 000000000000..9350f98841a4 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionController.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.model.app + +import android.app.AppOpsManager +import android.content.Context +import android.content.pm.ApplicationInfo +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +interface IAppOpsPermissionController { + val isAllowedFlow: Flow<Boolean> + fun setAllowed(allowed: Boolean) +} + +class AppOpsPermissionController( + context: Context, + private val app: ApplicationInfo, + appOps: AppOps, + private val permission: String, + private val packageManagers: IPackageManagers = PackageManagers, + private val appOpsController: IAppOpsController = AppOpsController(context, app, appOps), +) : IAppOpsPermissionController { + override val isAllowedFlow: Flow<Boolean> = appOpsController.modeFlow.map { mode -> + when (mode) { + AppOpsManager.MODE_ALLOWED -> true + + AppOpsManager.MODE_DEFAULT -> { + with(packageManagers) { app.hasGrantPermission(permission) } + } + + else -> false + } + }.conflate().onEach { Log.d(TAG, "isAllowed: $it") }.flowOn(Dispatchers.Default) + + override fun setAllowed(allowed: Boolean) { + appOpsController.setAllowed(allowed) + } + + private companion object { + private const val TAG = "AppOpsPermissionControl" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt index 68da1431c594..bededf03a0f4 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.Dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.android.settingslib.spa.framework.compose.LifecycleEffect import com.android.settingslib.spa.framework.compose.LogCompositions @@ -49,7 +50,6 @@ import com.android.settingslib.spaprivileged.model.app.AppListViewModel import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.IAppListViewModel import com.android.settingslib.spaprivileged.model.app.userId -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow private const val TAG = "AppList" @@ -95,9 +95,9 @@ internal fun <T : AppRecord> AppListInput<T>.AppListImpl( LogCompositions(TAG, config.userIds.toString()) val viewModel = viewModelSupplier() Column(Modifier.fillMaxSize()) { - val optionsState = viewModel.spinnerOptionsFlow.collectAsState(null, Dispatchers.IO) + val optionsState = viewModel.spinnerOptionsFlow.collectAsStateWithLifecycle(null) SpinnerOptions(optionsState, viewModel.optionFlow) - val appListData = viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO) + val appListData = viewModel.appListDataFlow.collectAsStateWithLifecycle(null) listModel.AppListWidget(appListData, header, bottomPadding, noItemMessage) } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt index 5db5eae22745..120b75ecb666 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt @@ -20,12 +20,14 @@ import android.app.AppOpsManager import android.content.Context import android.content.pm.ApplicationInfo import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.settingslib.spa.framework.util.asyncMapItem import com.android.settingslib.spa.framework.util.filterItem -import com.android.settingslib.spaprivileged.model.app.AppOpsController +import com.android.settingslib.spaprivileged.model.app.AppOps +import com.android.settingslib.spaprivileged.model.app.AppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.AppRecord -import com.android.settingslib.spaprivileged.model.app.IAppOpsController +import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers import kotlinx.coroutines.flow.Flow @@ -36,7 +38,7 @@ data class AppOpPermissionRecord( override val app: ApplicationInfo, val hasRequestBroaderPermission: Boolean, val hasRequestPermission: Boolean, - var appOpsController: IAppOpsController, + var appOpsPermissionController: IAppOpsPermissionController, ) : AppRecord abstract class AppOpPermissionListModel( @@ -44,11 +46,11 @@ abstract class AppOpPermissionListModel( private val packageManagers: IPackageManagers = PackageManagers, ) : TogglePermissionAppListModel<AppOpPermissionRecord> { - abstract val appOp: Int + abstract val appOps: AppOps abstract val permission: String override val enhancedConfirmationKey: String? - get() = AppOpsManager.opToPublicName(appOp) + get() = AppOpsManager.opToPublicName(appOps.op) /** * When set, specifies the broader permission who trumps the [permission]. @@ -65,27 +67,12 @@ abstract class AppOpPermissionListModel( */ open val permissionHasAppOpFlag: Boolean = true - open val modeForNotAllowed: Int = AppOpsManager.MODE_ERRORED - - /** - * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed. - * - * Security or privacy related app-ops should be set with setUidMode() instead of setMode(). - */ - open val setModeByUid = false - /** These not changeable packages will also be hidden from app list. */ private val notChangeablePackages = setOf("android", "com.android.systemui", context.packageName) - private fun createAppOpsController(app: ApplicationInfo) = - AppOpsController( - context = context, - app = app, - op = appOp, - setModeByUid = setModeByUid, - modeForNotAllowed = modeForNotAllowed, - ) + private fun createAppOpsPermissionController(app: ApplicationInfo) = + AppOpsPermissionController(context, app, appOps, permission) private fun createRecord( app: ApplicationInfo, @@ -98,7 +85,7 @@ abstract class AppOpPermissionListModel( app.hasRequestPermission(it) } ?: false, hasRequestPermission = hasRequestPermission, - appOpsController = createAppOpsController(app), + appOpsPermissionController = createAppOpsPermissionController(app), ) } @@ -131,14 +118,20 @@ abstract class AppOpPermissionListModel( override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) = recordListFlow.filterItem(::isChangeable) + /** + * Defining the default behavior as permissible as long as the package requested this permission + * (This means pre-M gets approval during install time; M apps gets approval during runtime). + */ @Composable - override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? = - isAllowed( - record = record, - appOpsController = record.appOpsController, - permission = permission, - packageManagers = packageManagers, - ) + override fun isAllowed(record: AppOpPermissionRecord): () -> Boolean? { + if (record.hasRequestBroaderPermission) { + // Broader permission trumps the specific permission. + return { true } + } + val isAllowed by record.appOpsPermissionController.isAllowedFlow + .collectAsStateWithLifecycle(initialValue = null) + return { isAllowed } + } override fun isChangeable(record: AppOpPermissionRecord) = record.hasRequestPermission && @@ -146,36 +139,6 @@ abstract class AppOpPermissionListModel( record.app.packageName !in notChangeablePackages override fun setAllowed(record: AppOpPermissionRecord, newAllowed: Boolean) { - record.appOpsController.setAllowed(newAllowed) - } -} - -/** - * Defining the default behavior as permissible as long as the package requested this permission - * (This means pre-M gets approval during install time; M apps gets approval during runtime). - */ -@Composable -internal fun isAllowed( - record: AppOpPermissionRecord, - appOpsController: IAppOpsController, - permission: String, - packageManagers: IPackageManagers = PackageManagers, -): () -> Boolean? { - if (record.hasRequestBroaderPermission) { - // Broader permission trumps the specific permission. - return { true } - } - - val mode = appOpsController.mode.collectAsStateWithLifecycle(initialValue = null) - return { - when (mode.value) { - null -> null - AppOpsManager.MODE_ALLOWED -> true - AppOpsManager.MODE_DEFAULT -> { - with(packageManagers) { record.app.hasGrantPermission(permission) } - } - - else -> false - } + record.appOpsPermissionController.setAllowed(newAllowed) } } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt index 5a6c0a1bf275..dd7c0368bf4b 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt @@ -27,8 +27,6 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.test.junit4.createComposeRule import androidx.lifecycle.testing.TestLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.settingslib.spa.testutils.delay -import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -71,9 +69,8 @@ class DisposableBroadcastReceiverAsUserTest { DisposableBroadcastReceiverAsUser(INTENT_FILTER, USER_HANDLE) {} } } - composeTestRule.delay() - assertThat(registeredBroadcastReceiver).isNotNull() + composeTestRule.waitUntil { registeredBroadcastReceiver != null } } @Test @@ -91,9 +88,8 @@ class DisposableBroadcastReceiverAsUserTest { } registeredBroadcastReceiver!!.onReceive(context, Intent()) - composeTestRule.delay() - assertThat(onReceiveIsCalled).isTrue() + composeTestRule.waitUntil { onReceiveIsCalled } } private companion object { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt index 91bbd9f9a5c1..74a7c146b2ab 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt @@ -27,16 +27,14 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spaprivileged.framework.common.appOpsManager import com.google.common.truth.Truth.assertThat -import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule -import org.mockito.kotlin.any -import org.mockito.kotlin.doNothing +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @@ -44,28 +42,18 @@ import org.mockito.kotlin.whenever class AppOpsControllerTest { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() - @Spy private val context: Context = ApplicationProvider.getApplicationContext() + private val appOpsManager = mock<AppOpsManager>() - @Mock private lateinit var appOpsManager: AppOpsManager + private val packageManager = mock<PackageManager>() - @Mock private lateinit var packageManager: PackageManager - - @Before - fun setUp() { - whenever(context.appOpsManager).thenReturn(appOpsManager) - whenever(context.packageManager).thenReturn(packageManager) - doNothing().whenever(packageManager) - .updatePermissionFlags(any(), any(), any(), any(), any()) + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { appOpsManager } doReturn appOpsManager + on { packageManager } doReturn packageManager } @Test fun setAllowed_setToTrue() { - val controller = - AppOpsController( - context = context, - app = APP, - op = OP, - ) + val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP)) controller.setAllowed(true) @@ -74,12 +62,7 @@ class AppOpsControllerTest { @Test fun setAllowed_setToFalse() { - val controller = - AppOpsController( - context = context, - app = APP, - op = OP, - ) + val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP)) controller.setAllowed(false) @@ -88,13 +71,11 @@ class AppOpsControllerTest { @Test fun setAllowed_setToFalseWithModeForNotAllowed() { - val controller = - AppOpsController( - context = context, - app = APP, - op = OP, - modeForNotAllowed = MODE_IGNORED, - ) + val controller = AppOpsController( + context = context, + app = APP, + appOps = AppOps(op = OP, modeForNotAllowed = MODE_IGNORED), + ) controller.setAllowed(false) @@ -103,13 +84,11 @@ class AppOpsControllerTest { @Test fun setAllowed_setToTrueByUid() { - val controller = - AppOpsController( - context = context, - app = APP, - op = OP, - setModeByUid = true, - ) + val controller = AppOpsController( + context = context, + app = APP, + appOps = AppOps(op = OP, setModeByUid = true), + ) controller.setAllowed(true) @@ -118,13 +97,11 @@ class AppOpsControllerTest { @Test fun setAllowed_setToFalseByUid() { - val controller = - AppOpsController( - context = context, - app = APP, - op = OP, - setModeByUid = true, - ) + val controller = AppOpsController( + context = context, + app = APP, + appOps = AppOps(op = OP, setModeByUid = true), + ) controller.setAllowed(false) @@ -135,12 +112,7 @@ class AppOpsControllerTest { fun getMode() { whenever(appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName)) .thenReturn(MODE_ALLOWED) - val controller = - AppOpsController( - context = context, - app = APP, - op = OP, - ) + val controller = AppOpsController(context = context, app = APP, appOps = AppOps(OP)) val mode = controller.getMode() diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt new file mode 100644 index 000000000000..9f80b92548d2 --- /dev/null +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsPermissionControllerTest.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.spaprivileged.model.app + +import android.app.AppOpsManager +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull +import com.android.settingslib.spaprivileged.framework.common.appOpsManager +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.runBlocking +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class AppOpsPermissionControllerTest { + + private val appOpsManager = mock<AppOpsManager>() + private val packageManager = mock<PackageManager>() + private val packageManagers = mock<IPackageManagers>() + private val appOpsController = mock<IAppOpsController>() + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { appOpsManager } doReturn appOpsManager + on { packageManager } doReturn packageManager + } + + @Test + fun isAllowedFlow_appOpsAllowed_returnTrue() = runBlocking { + appOpsController.stub { + on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ALLOWED) + } + val controller = AppOpsPermissionController( + context = context, + app = APP, + appOps = AppOps(op = OP), + permission = PERMISSION, + appOpsController = appOpsController, + ) + + val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() + + assertThat(isAllowed).isTrue() + } + + @Test + fun isAllowedFlow_appOpsDefaultAndPermissionGranted_returnTrue() = runBlocking { + appOpsController.stub { + on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT) + } + packageManagers.stub { + on { APP.hasGrantPermission(PERMISSION) } doReturn true + } + val controller = AppOpsPermissionController( + context = context, + app = APP, + appOps = AppOps(op = OP), + permission = PERMISSION, + packageManagers = packageManagers, + appOpsController = appOpsController, + ) + + val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() + + assertThat(isAllowed).isTrue() + } + + @Test + fun isAllowedFlow_appOpsDefaultAndPermissionNotGranted_returnFalse() = runBlocking { + appOpsController.stub { + on { modeFlow } doReturn flowOf(AppOpsManager.MODE_DEFAULT) + } + packageManagers.stub { + on { APP.hasGrantPermission(PERMISSION) } doReturn false + } + val controller = AppOpsPermissionController( + context = context, + app = APP, + appOps = AppOps(op = OP), + permission = PERMISSION, + packageManagers = packageManagers, + appOpsController = appOpsController, + ) + + val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() + + assertThat(isAllowed).isFalse() + } + + @Test + fun isAllowedFlow_appOpsError_returnFalse() = runBlocking { + appOpsController.stub { + on { modeFlow } doReturn flowOf(AppOpsManager.MODE_ERRORED) + } + val controller = AppOpsPermissionController( + context = context, + app = APP, + appOps = AppOps(op = OP), + permission = PERMISSION, + appOpsController = appOpsController, + ) + + val isAllowed = controller.isAllowedFlow.firstWithTimeoutOrNull() + + assertThat(isAllowed).isFalse() + } + + @Test + fun setAllowed_notSetModeByUid() { + val controller = AppOpsPermissionController( + context = context, + app = APP, + appOps = AppOps(op = OP, setModeByUid = false), + permission = PERMISSION, + ) + + controller.setAllowed(true) + + verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, AppOpsManager.MODE_ALLOWED) + } + + @Test + fun setAllowed_setModeByUid() { + val controller = AppOpsPermissionController( + context = context, + app = APP, + appOps = AppOps(op = OP, setModeByUid = true), + permission = PERMISSION, + ) + + controller.setAllowed(true) + + verify(appOpsManager).setUidMode(OP, APP.uid, AppOpsManager.MODE_ALLOWED) + } + + private companion object { + const val OP = 1 + const val PERMISSION = "Permission" + val APP = ApplicationInfo().apply { + packageName = "package.name" + uid = 123 + } + } +} diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt index 70b38feae9d5..cd747cc142c1 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsGlobalBooleanTest.kt @@ -102,7 +102,8 @@ class SettingsGlobalBooleanTest { delay(100) value = true - assertThat(listDeferred.await()).containsExactly(false, true).inOrder() + assertThat(listDeferred.await()) + .containsAtLeast(false, true).inOrder() } private companion object { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt index 29a89be87acd..ecc92f8f8d5c 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/settingsprovider/SettingsSecureBooleanTest.kt @@ -102,7 +102,8 @@ class SettingsSecureBooleanTest { delay(100) value = true - assertThat(listDeferred.await()).containsExactly(false, true).inOrder() + assertThat(listDeferred.await()) + .containsAtLeast(false, true).inOrder() } private companion object { diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt index bb25cf3b6d48..9d12fc7611cb 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt @@ -25,7 +25,8 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull import com.android.settingslib.spaprivileged.framework.common.appOpsManager -import com.android.settingslib.spaprivileged.model.app.IAppOpsController +import com.android.settingslib.spaprivileged.model.app.AppOps +import com.android.settingslib.spaprivileged.model.app.IAppOpsPermissionController import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.test.R import com.google.common.truth.Truth.assertThat @@ -39,7 +40,6 @@ import org.mockito.kotlin.doNothing import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock import org.mockito.kotlin.spy -import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) @@ -119,7 +119,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = false, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + appOpsPermissionController = FakeAppOpsPermissionController(false), ) val recordListFlow = listModel.filter(flowOf(USER_ID), flowOf(listOf(record))) @@ -135,7 +135,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ALLOWED), + appOpsPermissionController = FakeAppOpsPermissionController(true), ) val isAllowed = getIsAllowed(record) @@ -144,38 +144,6 @@ class AppOpPermissionAppListTest { } @Test - fun isAllowed_defaultAndHasGrantPermission() { - with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(true) } - val record = - AppOpPermissionRecord( - app = APP, - hasRequestBroaderPermission = false, - hasRequestPermission = true, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), - ) - - val isAllowed = getIsAllowed(record) - - assertThat(isAllowed).isTrue() - } - - @Test - fun isAllowed_defaultAndNotGrantPermission() { - with(packageManagers) { whenever(APP.hasGrantPermission(PERMISSION)).thenReturn(false) } - val record = - AppOpPermissionRecord( - app = APP, - hasRequestBroaderPermission = false, - hasRequestPermission = true, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), - ) - - val isAllowed = getIsAllowed(record) - - assertThat(isAllowed).isFalse() - } - - @Test fun isAllowed_broaderPermissionTrumps() { listModel.broaderPermission = BROADER_PERMISSION with(packageManagers) { @@ -187,7 +155,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = true, hasRequestPermission = false, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED), + appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isAllowed = getIsAllowed(record) @@ -202,7 +170,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_ERRORED), + appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isAllowed = getIsAllowed(record) @@ -217,7 +185,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = false, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) @@ -232,7 +200,7 @@ class AppOpPermissionAppListTest { app = NOT_CHANGEABLE_APP, hasRequestBroaderPermission = false, hasRequestPermission = true, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) @@ -247,7 +215,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) @@ -263,7 +231,7 @@ class AppOpPermissionAppListTest { app = APP, hasRequestBroaderPermission = true, hasRequestPermission = true, - appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT), + appOpsPermissionController = FakeAppOpsPermissionController(false), ) val isChangeable = listModel.isChangeable(record) @@ -273,28 +241,18 @@ class AppOpPermissionAppListTest { @Test fun setAllowed() { - val appOpsController = FakeAppOpsController(fakeMode = AppOpsManager.MODE_DEFAULT) + val appOpsPermissionController = FakeAppOpsPermissionController(false) val record = AppOpPermissionRecord( app = APP, hasRequestBroaderPermission = false, hasRequestPermission = true, - appOpsController = appOpsController, + appOpsPermissionController = appOpsPermissionController, ) listModel.setAllowed(record = record, newAllowed = true) - assertThat(appOpsController.setAllowedCalledWith).isTrue() - } - - @Test - fun setAllowed_setModeByUid() { - listModel.setModeByUid = true - val record = listModel.transformItem(APP) - - listModel.setAllowed(record = record, newAllowed = true) - - verify(appOpsManager).setUidMode(listModel.appOp, APP.uid, AppOpsManager.MODE_ALLOWED) + assertThat(appOpsPermissionController.setAllowedCalledWith).isTrue() } private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? { @@ -309,11 +267,9 @@ class AppOpPermissionAppListTest { override val switchTitleResId = R.string.test_app_op_permission_switch_title override val footerResId = R.string.test_app_op_permission_footer - override val appOp = AppOpsManager.OP_MANAGE_MEDIA + override val appOps = AppOps(AppOpsManager.OP_MANAGE_MEDIA) override val permission = PERMISSION override var broaderPermission: String? = null - - override var setModeByUid = false } private companion object { @@ -326,14 +282,12 @@ class AppOpPermissionAppListTest { } } -private class FakeAppOpsController(private val fakeMode: Int) : IAppOpsController { +private class FakeAppOpsPermissionController(allowed: Boolean) : IAppOpsPermissionController { var setAllowedCalledWith: Boolean? = null - override val mode = flowOf(fakeMode) + override val isAllowedFlow = flowOf(allowed) override fun setAllowed(allowed: Boolean) { setAllowedCalledWith = allowed } - - override fun getMode() = fakeMode } diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 89f54d9b3b3b..32557b979bf5 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -62,3 +62,20 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "allow_all_widgets_on_lockscreen_by_default" + namespace: "systemui" + description: "Allow all widgets on the lock screen by default." + bug: "328261690" +} + +flag { + name: "enable_determining_advanced_details_header_with_metadata" + namespace: "pixel_cross_device_control" + description: "Use metadata instead of device type to determine whether a bluetooth device should use advanced details header." + bug: "328556903" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig index 7aae1a6b4a59..6f614b372ea6 100644 --- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig +++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig @@ -31,3 +31,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "use_playback_info_for_routing_controls" + namespace: "media_solutions" + description: "Use app-provided playback info when providing media routing information." + bug: "333564788" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml index 964120461ddd..ad5337c3982f 100644 --- a/packages/SettingsLib/res/values-af/strings.xml +++ b/packages/SettingsLib/res/values-af/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Gekoppel (geen foon of media nie), battery <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">"Aktief. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktief. 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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktief. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktief. R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batterykrag"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Battery <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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> @@ -107,12 +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> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <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> @@ -290,9 +285,9 @@ <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Laat toe dat die selflaaiprogram ontsluit word"</string> <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"Laat OEM-ontsluit toe?"</string> <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"WAARSKUWING: Toestelbeskermingkenmerke sal nie op hierdie toestel werk terwyl hierdie instelling aangeskakel is nie."</string> - <string name="mock_location_app" msgid="6269380172542248304">"Kies skynliggingprogram"</string> + <string name="mock_location_app" msgid="6269380172542248304">"Kies skynliggingapp"</string> <string name="mock_location_app_not_set" msgid="6972032787262831155">"Geen skynliggingprogram gestel nie"</string> - <string name="mock_location_app_set" msgid="4706722469342913843">"Skynliggingprogram: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="mock_location_app_set" msgid="4706722469342913843">"Skynliggingapp: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="6829757985772659599">"Inligtingruiling"</string> <string name="wifi_display_certification" msgid="1805579519992520381">"Draadlose skermsertifisering"</string> <string name="wifi_verbose_logging" msgid="1785910450009679371">"Aktiveer Wi-Fi-woordryke aanmelding"</string> @@ -376,7 +371,7 @@ <string name="media_category" msgid="8122076702526144053">"Media"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"Monitering"</string> <string name="strict_mode" msgid="889864762140862437">"Strengmodus geaktiveer"</string> - <string name="strict_mode_summary" msgid="1838248687233554654">"Flits skerm as programme lang bewerkings uitvoer op die hoofdraad"</string> + <string name="strict_mode_summary" msgid="1838248687233554654">"Flits skerm as apps lang bewerkings uitvoer op die hoofdraad"</string> <string name="pointer_location" msgid="7516929526199520173">"Wyserligging"</string> <string name="pointer_location_summary" msgid="957120116989798464">"Skermlaag wys huidige raakdata"</string> <string name="show_touches" msgid="8437666942161289025">"Wys tikke"</string> @@ -450,7 +445,7 @@ <string name="inactive_app_inactive_summary" msgid="3161222402614236260">"Onaktief. Tik om te wissel."</string> <string name="inactive_app_active_summary" msgid="8047630990208722344">"Aktief. Tik om te wissel."</string> <string name="standby_bucket_summary" msgid="5128193447550429600">"Programbystandstatus:<xliff:g id="BUCKET"> %s</xliff:g>"</string> - <string name="transcode_settings_title" msgid="2581975870429850549">"Mediakodewisselinginstellings"</string> + <string name="transcode_settings_title" msgid="2581975870429850549">"Mediatranskoderinginstellings"</string> <string name="transcode_user_control" msgid="6176368544817731314">"Ignoreer kodewisselingverstekke"</string> <string name="transcode_enable_all" msgid="2411165920039166710">"Aktiveer kodewisseling"</string> <string name="transcode_default" msgid="3784803084573509491">"Aanvaar dat programme moderne formate steun"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutrum"</string> <string name="feminine" msgid="1529155595310784757">"Vroulik"</string> <string name="masculine" msgid="4653978041013996303">"Manlik"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Stelselopdaterings"</string> </resources> diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml index 51cf4411b1cd..b65fdc056534 100644 --- a/packages/SettingsLib/res/values-am/strings.xml +++ b/packages/SettingsLib/res/values-am/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ግራ፦ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ባትሪ"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ቀኝ፦ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ባትሪ"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ንቁ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ተቀምጧል"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ገቢር (ግራ ብቻ)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"ገቢር (ቀኝ ብቻ)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"ገቢር (ግራ እና ቀኝ)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"ገቢር (ሚዲያ ብቻ)። <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ባትሪ።"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"ገቢር (ሚዲያ ብቻ)። ግ፦ <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_battery_level_lea_support" msgid="5968584103507988820">"ተገናኝቷል (የድምፅ ማጋራት ይደግፋል)፣ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ባትሪ።"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"ተገናኝቷል (የድምፅ ማጋራት ይደግፋል) ግ፦ <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"ተገናኝቷል (የድምፅ ማጋራት ይደግፋል)። ግራ፦<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ባትሪ።"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"ተገናኝቷል (የድምፅ ማጋራት ይደግፋል)። ቀኝ፦ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ባትሪ።"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"ተገናኝቷል (የድምፅ ማጋራት ይደግፋል)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"ገቢር (ሚዲያ ብቻ)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"ድምፅ ማጋራትን ይደግፋል"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"ገቢር (ሚዲያ ብቻ)፣ ግራ ብቻ"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ገለልተኛ"</string> <string name="feminine" msgid="1529155595310784757">"እንስት"</string> <string name="masculine" msgid="4653978041013996303">"ተባዕታይ"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"የሥርዓት ዝማኔዎች"</string> </resources> diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml index 4b1e925582e3..cfa5b1450e39 100644 --- a/packages/SettingsLib/res/values-ar/strings.xml +++ b/packages/SettingsLib/res/values-ar/strings.xml @@ -96,10 +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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"مستوى الشحن في سماعة الرأس اليمنى: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"نشط"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"محفوظ"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"سماعة الأذن الطبية نشطة (اليسرى فقط)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"سماعة الأذن الطبية نشطة (اليمنى فقط)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"سماعتا الأذن الطبيتان نشطتان (اليسرى واليمنى)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"البلوتوث نشِط (للوسائط فقط). مستوى شحن البطارية: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"البلوتوث نشِط (للوسائط فقط)، مستوى الشحن في سماعة الرأس اليسرى: <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_battery_level_lea_support" msgid="5968584103507988820">"البلوتوث متصل (ميزة \"مشاركة الصوت\" متاحة). مستوى شحن البطارية: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> @@ -199,7 +194,7 @@ <string name="launch_defaults_some" msgid="3631650616557252926">"تم ضبط بعض الإعدادات التلقائية"</string> <string name="launch_defaults_none" msgid="8049374306261262709">"لم يتم ضبط إعدادات تلقائية"</string> <string name="tts_settings" msgid="8130616705989351312">"إعدادات تحويل النص إلى كلام"</string> - <string name="tts_settings_title" msgid="7602210956640483039">"الصوت عند تحويل النص إلى كلام"</string> + <string name="tts_settings_title" msgid="7602210956640483039">"الرد الصوتي"</string> <string name="tts_default_rate_title" msgid="3964187817364304022">"سرعة الكلام"</string> <string name="tts_default_rate_summary" msgid="3781937042151716987">"سرعة قول الكلام"</string> <string name="tts_default_pitch_title" msgid="6988592215554485479">"درجة الصوت"</string> @@ -213,7 +208,7 @@ <string name="tts_install_data_title" msgid="1829942496472751703">"تثبيت البيانات الصوتية"</string> <string name="tts_install_data_summary" msgid="3608874324992243851">"تثبيت البيانات الصوتية المطلوبة لتجميع الكلام"</string> <string name="tts_engine_security_warning" msgid="3372432853837988146">"ربما يمكن لمحرك اصطناع الحديث جمع كل النص التي سيتم نطقه، بما في ذلك البيانات الشخصية مثل كلمات المرور وأرقام بطاقة الائتمان. يتم إحضار ذلك من المحرك <xliff:g id="TTS_PLUGIN_ENGINE_NAME">%s</xliff:g>. هل تريد تفعيل استخدام محرك اصطناع الحديث هذا؟"</string> - <string name="tts_engine_network_required" msgid="8722087649733906851">"تتطلب هذه اللغة اتصال شبكة سليمًا لتحويل النص إلى كلام."</string> + <string name="tts_engine_network_required" msgid="8722087649733906851">"تتطلب هذه اللغة اتصال شبكة سليمًا لاستخدام ميزة الرد الصوتي."</string> <string name="tts_default_sample_string" msgid="6388016028292967973">"هذا مثال لتركيب الكلام"</string> <string name="tts_status_title" msgid="8190784181389278640">"حالة اللغة التلقائية"</string> <string name="tts_status_ok" msgid="8583076006537547379">"<xliff:g id="LOCALE">%1$s</xliff:g> متوافقة تمامًا"</string> @@ -237,10 +232,10 @@ <item msgid="4446831566506165093">"350%"</item> <item msgid="6946761421234586000">"400%"</item> </string-array> - <string name="choose_profile" msgid="343803890897657450">"اختيار ملف شخصي"</string> - <string name="category_personal" msgid="6236798763159385225">"الحسابات الشخصية"</string> - <string name="category_work" msgid="4014193632325996115">"حسابات العمل"</string> - <string name="category_private" msgid="4244892185452788977">"ملف شخصي"</string> + <string name="choose_profile" msgid="343803890897657450">"تحديد الملف"</string> + <string name="category_personal" msgid="6236798763159385225">"الملف الشخصي"</string> + <string name="category_work" msgid="4014193632325996115">"ملف العمل"</string> + <string name="category_private" msgid="4244892185452788977">"الملف الخاص"</string> <string name="category_clone" msgid="1554511758987195974">"استنساخ"</string> <string name="development_settings_title" msgid="140296922921597393">"خيارات المطورين"</string> <string name="development_settings_enable" msgid="4285094651288242183">"تفعيل خيارات المطورين"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"صيغة مخاطبة محايدة"</string> <string name="feminine" msgid="1529155595310784757">"صيغة مخاطبة مؤنثة"</string> <string name="masculine" msgid="4653978041013996303">"صيغة مخاطبة مذكّرة"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"تحديثات النظام"</string> </resources> diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml index 13b639a0f5cf..debef2c723c8 100644 --- a/packages/SettingsLib/res/values-as/strings.xml +++ b/packages/SettingsLib/res/values-as/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"বাকী আছে: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> বেটাৰী"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"সোঁ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> বেটাৰী"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"সক্ৰিয়"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ছেভ কৰা হৈছে"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"সক্ৰিয় হৈ আছে (কেৱল বাওঁফালৰটো)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"সক্ৰিয় হৈ আছে (কেৱল সোঁফালৰটো)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"সক্ৰিয় হৈ আছে (বাওঁফালৰটো আৰু সোঁফালৰটো)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"সক্ৰিয় হৈ আছে (কেৱল মিডিয়া)। <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> বেটাৰী।"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"সক্ৰিয় হৈ আছে (কেৱল মিডিয়া)। বাওঁ: <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_battery_level_lea_support" msgid="5968584103507988820">"সংযুক্ত হৈ আছে (অডিঅ’ শ্বেয়াৰিং সমৰ্থন কৰে), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> বেটাৰী।"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"সংযুক্ত হৈ আছে (অডিঅ’ শ্বেয়াৰিং সমৰ্থন কৰে), বাওঁ: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"সংযুক্ত হৈ আছে (অডিঅ’ শ্বেয়াৰিং সমৰ্থন কৰে)। বাকী আছে: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> বেটাৰী।"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"সংযুক্ত হৈ আছে (অডিঅ’ শ্বেয়াৰিং সমৰ্থন কৰে)। সোঁ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> বেটাৰী।"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"সংযুক্ত হৈ আছে (অডিঅ’ শ্বেয়াৰিং সমৰ্থন কৰে)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"সক্ৰিয় হৈ আছে (কেৱল মিডিয়া)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"অডিঅ’ শ্বেয়াৰিং সমৰ্থন কৰে"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"সক্ৰিয় হৈ আছে (কেৱল মিডিয়া), কেৱল বাওঁ"</string> @@ -388,7 +371,7 @@ <string name="media_category" msgid="8122076702526144053">"মিডিয়া"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"নিৰীক্ষণ কৰি থকা হৈছে"</string> <string name="strict_mode" msgid="889864762140862437">"কঠোৰ ম’ড সক্ষম কৰা হৈছে"</string> - <string name="strict_mode_summary" msgid="1838248687233554654">"যেতিয়া এপ্সমূহে মুখ্য থ্ৰেডত দীঘলীয়া কাৰ্যকলাপ চলাই, তেতিয়া স্ক্ৰীন ফ্লাশ্ব কৰক"</string> + <string name="strict_mode_summary" msgid="1838248687233554654">"যেতিয়া এপ্সমূহে মুখ্য থ্ৰেডত দীঘলীয়া কাৰ্যকলাপ চলায়, তেতিয়া স্ক্ৰীন ফ্লাশ্ব কৰক"</string> <string name="pointer_location" msgid="7516929526199520173">"পইণ্টাৰৰ অৱস্থান"</string> <string name="pointer_location_summary" msgid="957120116989798464">"চলিত স্পৰ্শ-বিষয়ক তথ্যসহ স্ক্ৰীন অভাৰলে’"</string> <string name="show_touches" msgid="8437666942161289025">"টেপসমূহ দেখুৱাওক"</string> @@ -429,7 +412,7 @@ <string name="overlay_display_devices_title" msgid="5411894622334469607">"গৌণ প্ৰদৰ্শনৰ নকল বনাওক"</string> <string name="debug_applications_category" msgid="5394089406638954196">"এপ্সমূহ"</string> <string name="immediately_destroy_activities" msgid="1826287490705167403">"কাৰ্যকলাপসমূহ নাৰাখিব"</string> - <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"ব্যৱহাৰকাৰী ওলোৱাৰ লগে লগে সকলো কাৰ্যকলাপ মচক"</string> + <string name="immediately_destroy_activities_summary" msgid="6289590341144557614">"ব্যৱহাৰকাৰী ওলোৱাৰ লগে লগে আটাইবোৰ কাৰ্যকলাপ মচক"</string> <string name="app_process_limit_title" msgid="8361367869453043007">"নেপথ্যত চলা প্ৰক্ৰিয়াৰ সীমা"</string> <string name="show_all_anrs" msgid="9160563836616468726">"নেপথ্য এএনআৰবোৰ দেখুৱাওক"</string> <string name="show_all_anrs_summary" msgid="8562788834431971392">"নেপথ্য এপসমূহৰ বাবে এপে সঁহাৰি দিয়া নাই ডায়ল\'গ প্ৰদৰ্শন কৰক"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ক্লীৱ লিংগ"</string> <string name="feminine" msgid="1529155595310784757">"নাৰী-বিষয়ক"</string> <string name="masculine" msgid="4653978041013996303">"পুৰুষ-বিষয়ক"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"ছিষ্টেম আপডে’ট"</string> </resources> diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml index a3c0aac71ff6..9541a98cf01a 100644 --- a/packages/SettingsLib/res/values-az/strings.xml +++ b/packages/SettingsLib/res/values-az/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Qoşuludur (telefon və ya media yoxdur), batareya <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">"Aktiv. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktiv. Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batareya."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktiv. Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktiv. Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batareya: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batareya."</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Yadda saxlandı"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiv (yalnız sol)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktiv (yalnız sağ)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktiv (sol və sağ)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktiv (yalnız media). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiv (yalnız media). Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batareya."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Qoşulub (audio paylaşma dəstəklənir). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batareya."</string> @@ -240,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Profil seçin"</string> <string name="category_personal" msgid="6236798763159385225">"Şəxsi"</string> <string name="category_work" msgid="4014193632325996115">"İş"</string> - <string name="category_private" msgid="4244892185452788977">"Şəxsi"</string> + <string name="category_private" msgid="4244892185452788977">"Məxfi"</string> <string name="category_clone" msgid="1554511758987195974">"Klonlayın"</string> <string name="development_settings_title" msgid="140296922921597393">"Developer seçimləri"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Developer variantlarını aktiv edin"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neytral"</string> <string name="feminine" msgid="1529155595310784757">"Qadın"</string> <string name="masculine" msgid="4653978041013996303">"Kişi"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Sistem güncəllənmələri"</string> </resources> diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml index 24830b44eb1e..eda08fb0090a 100644 --- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml +++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Povezano (bez telefona), nivo baterije je <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">"Povezano (bez medija), nivo baterije je <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">"Povezano (bez telefona ili medija), nivo baterije je <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktivno. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktivno. Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktivno. Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktivno. Desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Nivo baterije je <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Baterija, <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktivan"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Sačuvano"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktivno (samo levo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktivno (samo desno)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktivno (levo i desno)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktivno (samo za medije). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktivno (samo za medije). Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Povezano (podržava deljenje zvuka), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Povezano (podržava deljenje zvuka), levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Povezano (podržava deljenje zvuka). Levo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Povezano (podržava deljenje zvuka). Desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Povezano (podržava deljenje zvuka)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktivan (samo za medije)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Podržava deljenje zvuka"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktivan (samo za medije), samo levo"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Srednji rod"</string> <string name="feminine" msgid="1529155595310784757">"Ženski rod"</string> <string name="masculine" msgid="4653978041013996303">"Muški rod"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Ažuriranja sistema."</string> </resources> diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml index 986c0890c61e..1c6705e69efb 100644 --- a/packages/SettingsLib/res/values-be/strings.xml +++ b/packages/SettingsLib/res/values-be/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (левы навушнік)"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (правы навушнік)"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Уключана"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Захавана"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Выкарыстоўваецца (толькі левы навушнік)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Выкарыстоўваецца (толькі правы навушнік)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Выкарыстоўваецца (левы і правы навушнікі)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Выкарыстоўваецца (толькі для мультымедыя). Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Выкарыстоўваецца (толькі для мультымедыя). Зарад акумулятара: <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_battery_level_lea_support" msgid="5968584103507988820">"Падключана (падтрымліваецца абагульванне аўдыя). Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Падключана (падтрымліваецца абагульванне аўдыя). Зарад акумулятара: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Падключана (падтрымліваецца абагульванне аўдыя). Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (левы навушнік)."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Падключана (падтрымліваецца абагульванне аўдыя). Зарад акумулятара: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (правы навушнік)."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Падключана (падтрымліваецца абагульванне аўдыя)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Выкарыстоўваецца (толькі для мультымедыя)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Падтрымліваецца абагульванне аўдыя"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Выкарыстоўваецца (толькі для мультымедыя), толькі левы навушнік"</string> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Выбраць профіль"</string> <string name="category_personal" msgid="6236798763159385225">"Асабісты"</string> <string name="category_work" msgid="4014193632325996115">"Працоўны"</string> - <string name="category_private" msgid="4244892185452788977">"Прыватныя"</string> + <string name="category_private" msgid="4244892185452788977">"Прыватны"</string> <string name="category_clone" msgid="1554511758987195974">"Клон"</string> <string name="development_settings_title" msgid="140296922921597393">"Параметры распрацоўшчыка"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Уключыць параметры распрацоўшчыка"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Ніякі"</string> <string name="feminine" msgid="1529155595310784757">"Жаночы"</string> <string name="masculine" msgid="4653978041013996303">"Мужчынскі"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Абнаўленні сістэмы"</string> </resources> diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml index 709707873062..d6794a240054 100644 --- a/packages/SettingsLib/res/values-bg/strings.xml +++ b/packages/SettingsLib/res/values-bg/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"За ляво ухо. Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"За дясно ухо. Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активно"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Запазено"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Активно (само лявото)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Активно (само дясното)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Активно (лявото и дясното)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Активно (само за мултимедия). Батерия – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Активно (само за мултимедия). Л: батерия – <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_battery_level_lea_support" msgid="5968584103507988820">"Свързано (поддържа споделяне на звука). Батерия – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Свързано (поддържа споделяне на звука). Л: батерия – <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Свързано (поддържа споделяне на звука). За ляво ухо. Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Свързано (поддържа споделяне на звука). За дясно ухо. Батерия: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Свързано (поддържа споделяне на звука)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Активно (само за мултимедия)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Поддържа споделяне на звука"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Активно (само за мултимедия), само лявата"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Среден род"</string> <string name="feminine" msgid="1529155595310784757">"Женски род"</string> <string name="masculine" msgid="4653978041013996303">"Мъжки род"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Системни актуализации"</string> </resources> diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml index e4637fe04690..57bc4010e5c4 100644 --- a/packages/SettingsLib/res/values-bn/strings.xml +++ b/packages/SettingsLib/res/values-bn/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"বাঁদিক: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ব্যাটারি"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ডানদিক: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ব্যাটারি"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"চালু আছে"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"সেভ করা আছে"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"চালু আছে (শুধু বাঁদিক)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"চালু আছে (শুধু ডানদিক)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"চালু আছে (বাঁদিক ও ডানদিক)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"চালু আছে (শুধুমাত্র মিডিয়া)। <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ব্যাটারি।"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"চালু আছে (শুধুমাত্র মিডিয়া), বাঁদিক: <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_battery_level_lea_support" msgid="5968584103507988820">"কানেক্ট করা আছে (অডিও শেয়ারিংয়ে কাজ করে), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ব্যাটারি।"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"কানেক্ট করা আছে (অডিও শেয়ারিংয়ে কাজ করে), বাঁদিক: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"কানেক্ট করা আছে (অডিও শেয়ারিংয়ে কাজ করে)। বাঁদিক: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ব্যাটারি।"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"কানেক্ট করা আছে (অডিও শেয়ারিংয়ে কাজ করে)। ডানদিকে: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ব্যাটারি।"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"কানেক্ট করা আছে (অডিও শেয়ারিংয়ে কাজ করে)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"চালু আছে (শুধুমাত্র মিডিয়া)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"অডিও শেয়ারিংয়ে কাজ করে"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"চালু আছে (শুধুমাত্র মিডিয়া), শুধুমাত্র বাঁদিক"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ক্লীব"</string> <string name="feminine" msgid="1529155595310784757">"স্ত্রী"</string> <string name="masculine" msgid="4653978041013996303">"পুং"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"সিস্টেম আপডেট"</string> </resources> diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml index e71c2ec7c69e..9d914db7b9a5 100644 --- a/packages/SettingsLib/res/values-bs/strings.xml +++ b/packages/SettingsLib/res/values-bs/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Povezano (bez telefona), baterija <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">"Povezano (bez medija), baterija <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">"Povezano (bez telefona ili medija), baterija <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktivno. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktivno. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> baterije, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktivno. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktivno. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Baterija <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> baterije, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Lijevo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktivan"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Sačuvano"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktivno (samo lijevo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktivno (samo desno)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktivno (lijevo i desno)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktivno (samo za medijski sadržaj). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktivno (samo za medijski sadržaj). L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> baterije, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Povezano (podržava dijeljenje zvuka). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Povezano (podržava dijeljenje zvuka). L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> baterije, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Povezano (podržava dijeljenje zvuka). Lijevo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Povezano (podržava dijeljenje zvuka). Desno: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Povezano (podržava dijeljenje zvuka)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktivno (samo za medijski sadržaj)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Podržava dijeljenje zvuka"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktivno (samo za medijski sadržaj), samo lijevo"</string> @@ -418,7 +401,7 @@ <string name="force_msaa" msgid="4081288296137775550">"Prinudno primijeni 4x MSAA"</string> <string name="force_msaa_summary" msgid="9070437493586769500">"Omogućava 4x MSAA u OpenGL ES 2.0 aplikacijama"</string> <string name="show_non_rect_clip" msgid="7499758654867881817">"Otkl. greške na operac. nepravoug. isjecanja"</string> - <string name="track_frame_time" msgid="522674651937771106">"Profil HWUI iscrtavanja"</string> + <string name="track_frame_time" msgid="522674651937771106">"Iscrtavanje pomoću HWUI-a"</string> <string name="enable_gpu_debug_layers" msgid="4986675516188740397">"Omogući slojeve za otklanjanje grešaka na GPU-u"</string> <string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"Omogućite slojeve za otkl. grešaka na GPU-u za apl. za otkl. grešaka"</string> <string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"Omogući opširni zapisnik"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Srednji rod"</string> <string name="feminine" msgid="1529155595310784757">"Ženski rod"</string> <string name="masculine" msgid="4653978041013996303">"Muški rod"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Ažuriranja sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml index 09b4297714e6..eb7bd7bd7037 100644 --- a/packages/SettingsLib/res/values-ca/strings.xml +++ b/packages/SettingsLib/res/values-ca/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> connectat (sense accés al telèfon), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> connectat (sense accés al contingut multimèdia), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> connectat (sense accés al telèfon ni al contingut multimèdia), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Actiu. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Actiu. E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Actiu. E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Actiu. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Esquerre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Dret: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Actiu"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Desat"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Actiu (només l\'esquerre)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Actiu (només el dret)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Actiu (esquerre i dret)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Actiu (només contingut multimè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">"Actiu (només contingut multimèdia), E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Connectat (admet compartició d\'à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">"Connectat (admet compartició d\'àudio), E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria, D: <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">"Connectat (admet compartició d\'àudio). Esquerre: <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">"Connectat (admet compartició d\'àudio). Dret: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Connectat (admet compartició d\'àudio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Actiu (només contingut multimèdia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Admet compartició d\'àudio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Actiu (només contingut multimèdia), només esquerre"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutre"</string> <string name="feminine" msgid="1529155595310784757">"Femení"</string> <string name="masculine" msgid="4653978041013996303">"Masculí"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Actualitzacions del sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml index b2d75bd4a29f..6fa6e12eabd2 100644 --- a/packages/SettingsLib/res/values-cs/strings.xml +++ b/packages/SettingsLib/res/values-cs/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Připojeno k zařízení <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (bez telefonu), úroveň baterie <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Připojeno k zařízení <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (bez médií), úroveň baterie <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Připojeno k zařízení <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (bez telefonu a médií), úroveň baterie <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktivní. Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktivní. Baterie: L <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktivní. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterie"</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktivní. P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterie"</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterie"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Baterie <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Baterie: L <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Levá strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterie"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Pravá strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterie"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktivní"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Uloženo"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktivní (pouze levé)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktivní (pouze pravé)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktivní (levé a pravé)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktivní (pouze média). Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktivní (pouze média), baterie: L <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Připojeno (podporuje sdílení zvuku), baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Připojeno (podporuje sdílení zvuku), baterie: L <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Připojeno (podporuje sdílení zvuku). Levá strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterie"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Připojeno (podporuje sdílení zvuku). Pravá strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterie."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Připojeno (podporuje sdílení zvuku)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktivní (pouze média)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Podporuje sdílení zvuku"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktivní (pouze média), pouze levé"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Střední rod"</string> <string name="feminine" msgid="1529155595310784757">"Ženský rod"</string> <string name="masculine" msgid="4653978041013996303">"Mužský rod"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Aktualizace systému"</string> </resources> diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml index 0f92316e038d..fc2191cf87a1 100644 --- a/packages/SettingsLib/res/values-da/strings.xml +++ b/packages/SettingsLib/res/values-da/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Forbundet med <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (ingen telefon) – batteriniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Forbundet med <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (ingen medier) – batteriniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Forbundet med <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (ingen telefon eller medier) – batteriniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktiveret. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktiveret. V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktiveret. V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktiveret. H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batteri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Venstre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Højre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gemt"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiveret (kun venstre)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktiveret (kun højre)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktiveret (venstre og højre)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktiveret (kun for medier). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiveret (kun for medier), V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Forbundet (understøtter lyddeling). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Forbundet (understøtter lyddeling), V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Forbundet (understøtter lyddeling). Venstre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Forbundet (understøtter lyddeling). Højre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Forbundet (understøtter lyddeling)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktiveret (kun for medier)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Understøtter lyddeling"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktiveret (kun for medier), kun venstre"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutrum"</string> <string name="feminine" msgid="1529155595310784757">"Femininum"</string> <string name="masculine" msgid="4653978041013996303">"Maskulinum"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Systemopdateringer"</string> </resources> diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml index c8d7acdccc18..793560d786e3 100644 --- a/packages/SettingsLib/res/values-de/strings.xml +++ b/packages/SettingsLib/res/values-de/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Mit <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> verbunden (kein Telefon-Audio), Akkustand bei <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Mit <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> verbunden (kein Medien-Audio), Akkustand bei <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Mit <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> verbunden (weder Telefon- noch Medien-Audio), Akkustand bei <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktiv. Akku: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktiv. Akku links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Akku rechts: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktiv. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> Akku."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktiv. R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> Akku."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Akkustand: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Akku – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Akku links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Akku rechts: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Akku links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Akku rechts: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gespeichert"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiv (nur links)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktiv (nur rechts)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktiv (links und rechts)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktiv (nur Medien). Akku: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiv (nur Medien). Akku links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Akku rechts: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Verbunden (unterstützt Audiofreigabe). Akku: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Verbunden (unterstützt Audiofreigabe). Akku links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Akku rechts: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Verbunden (unterstützt Audiofreigabe). Akku links: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Verbunden (unterstützt Audiofreigabe). Akku rechts: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Verbunden (unterstützt Audiofreigabe)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktiv (nur Medien)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Unterstützt Audiofreigabe"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktiv (nur Medien), nur links"</string> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Profil auswählen"</string> <string name="category_personal" msgid="6236798763159385225">"Privat"</string> <string name="category_work" msgid="4014193632325996115">"Geschäftlich"</string> - <string name="category_private" msgid="4244892185452788977">"Privat"</string> + <string name="category_private" msgid="4244892185452788977">"Vertraulich"</string> <string name="category_clone" msgid="1554511758987195974">"Klonen"</string> <string name="development_settings_title" msgid="140296922921597393">"Entwickleroptionen"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Entwickleroptionen aktivieren"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutrum"</string> <string name="feminine" msgid="1529155595310784757">"Feminin"</string> <string name="masculine" msgid="4653978041013996303">"Maskulin"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Systemupdates"</string> </resources> diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml index 2cc9350e7426..6bcdc81b5a02 100644 --- a/packages/SettingsLib/res/values-el/strings.xml +++ b/packages/SettingsLib/res/values-el/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Αριστερά: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> μπαταρία"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Δεξιά: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> μπαταρία"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ενεργό"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Αποθηκεύτηκε"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ενεργό (μόνο το αριστερό)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Ενεργό (μόνο το δεξί)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ενεργό (αριστερό και δεξί)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ενεργό (μόνο για μέσα). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> μπαταρία."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Ενεργό (μόνο για μέσα). Α: <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_battery_level_lea_support" msgid="5968584103507988820">"Συνδεδεμένο (υποστηρίζει κοινή χρήση ήχου). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> μπαταρία."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Συνδεδεμένο (υποστηρίζει κοινή χρήση ήχου). Α: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Συνδεδεμένο (υποστηρίζει κοινή χρήση ήχου). Αριστερά: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> μπαταρία."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Συνδεδεμένο (υποστηρίζει κοινή χρήση ήχου). Δεξιά: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> μπαταρία."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Συνδεδεμένο (υποστηρίζει κοινή χρήση ήχου)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ενεργό (μόνο για μέσα)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Υποστηρίζει κοινή χρήση ήχου"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ενεργό (μόνο για μέσα), μόνο αριστερό"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Ουδέτερο"</string> <string name="feminine" msgid="1529155595310784757">"Θηλυκό"</string> <string name="masculine" msgid="4653978041013996303">"Αρσενικό"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Ενημερώσεις συστήματος"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml index 2d80c355f284..83c1bdcf1174 100644 --- a/packages/SettingsLib/res/values-en-rAU/strings.xml +++ b/packages/SettingsLib/res/values-en-rAU/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Connected (no phone or media), battery <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">"Active. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Active. 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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Active. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Active. R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Battery <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Right: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Active"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Saved"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Active (left only)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Active (right only)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Active (left and right)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Active (media only). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Active (media only). 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">"Connected (supports audio sharing). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neuter"</string> <string name="feminine" msgid="1529155595310784757">"Feminine"</string> <string name="masculine" msgid="4653978041013996303">"Masculine"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"System updates"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml index bc08544434df..59fe8da59c2f 100644 --- a/packages/SettingsLib/res/values-en-rCA/strings.xml +++ b/packages/SettingsLib/res/values-en-rCA/strings.xml @@ -721,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neuter"</string> <string name="feminine" msgid="1529155595310784757">"Feminine"</string> <string name="masculine" msgid="4653978041013996303">"Masculine"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"System Updates"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml index 2d80c355f284..83c1bdcf1174 100644 --- a/packages/SettingsLib/res/values-en-rGB/strings.xml +++ b/packages/SettingsLib/res/values-en-rGB/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Connected (no phone or media), battery <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">"Active. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Active. 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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Active. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Active. R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Battery <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Right: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Active"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Saved"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Active (left only)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Active (right only)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Active (left and right)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Active (media only). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Active (media only). 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">"Connected (supports audio sharing). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neuter"</string> <string name="feminine" msgid="1529155595310784757">"Feminine"</string> <string name="masculine" msgid="4653978041013996303">"Masculine"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"System updates"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml index 2d80c355f284..83c1bdcf1174 100644 --- a/packages/SettingsLib/res/values-en-rIN/strings.xml +++ b/packages/SettingsLib/res/values-en-rIN/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Connected (no phone or media), battery <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">"Active. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Active. 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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Active. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Active. R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Battery <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Right: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Active"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Saved"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Active (left only)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Active (right only)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Active (left and right)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Active (media only). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Active (media only). 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">"Connected (supports audio sharing). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> battery."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neuter"</string> <string name="feminine" msgid="1529155595310784757">"Feminine"</string> <string name="masculine" msgid="4653978041013996303">"Masculine"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"System updates"</string> </resources> diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml index b123a1349403..e7f6d99a8a08 100644 --- a/packages/SettingsLib/res/values-en-rXC/strings.xml +++ b/packages/SettingsLib/res/values-en-rXC/strings.xml @@ -721,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neuter"</string> <string name="feminine" msgid="1529155595310784757">"Feminine"</string> <string name="masculine" msgid="4653978041013996303">"Masculine"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"System Updates"</string> </resources> diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml index 5142f249b3fe..1a0fec5d431a 100644 --- a/packages/SettingsLib/res/values-es-rUS/strings.xml +++ b/packages/SettingsLib/res/values-es-rUS/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Conectado (sin teléfono) a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería)"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Conectado (sin archivos multimedia) a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería)"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Conectado (sin teléfono ni archivos multimedia) a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería)"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Activado. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Activado. I: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>; D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Activo. I: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Activo. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batería: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> - <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Activado"</string> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"I: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>; D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> + <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Activo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Guardado"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Activo (solo izquierdo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Activo (solo derecho)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Activos (izquierdo y derecho)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Activado (solo para contenido multimedia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Activo (solo para contenido multimedia); I: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>; D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (admite el uso compartido de audio); <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (admite el uso compartido de audio); I: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>; D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (admite el uso compartido de audio). Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (admite el uso compartido de audio). Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (admite el uso compartido de audio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Activo (solo para contenido multimedia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Admite el uso compartido de audio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Activo (solo para contenido multimedia); solo izquierdo"</string> @@ -142,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> @@ -298,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> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutro"</string> <string name="feminine" msgid="1529155595310784757">"Femenino"</string> <string name="masculine" msgid="4653978041013996303">"Masculino"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Actualizaciones del sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml index 7dc1225210ba..69c804e9e83b 100644 --- a/packages/SettingsLib/res/values-es/strings.xml +++ b/packages/SettingsLib/res/values-es/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Conectado (sin audio de teléfono) a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería)"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Conectado (sin audio multimedia) a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería)"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Conectado (sin audio de teléfono ni multimedia) a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería)"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Activo. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Activo. Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Activo. I: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Activo. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batería <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Activo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Guardado"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Activo (solo izquierdo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Activo (solo derecho)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Activo (izquierdo y derecho)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Activo (solo multimedia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Activo (solo multimedia). Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (permite compartir audio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (admite Compartir audio). Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (admite Compartir audio). Izquierdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (admite Compartir audio). Derecho: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (admite Compartir audio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Activo (solo multimedia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Permite compartir audio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Activo (solo multimedia), solo el izquierdo"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutro"</string> <string name="feminine" msgid="1529155595310784757">"Femenino"</string> <string name="masculine" msgid="4653978041013996303">"Masculino"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Actualizaciones del sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml index 1236629b86f2..fd5762b3b6a2 100644 --- a/packages/SettingsLib/res/values-et/strings.xml +++ b/packages/SettingsLib/res/values-et/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Ühendatud (telefoni ega meediat pole), aku <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">"Aktiivne. Aku <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktiivne. Aku: V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktiivne. Vasak: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> akutoidet."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktiivne. Parem: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> akutoidet."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> akut"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Akutase: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Aku: V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Parem: aku <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiivne"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Salvestatud"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiivne (ainult vasak)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktiivne (ainult parem)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktiivne (vasak ja parem)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktiivne (ainult meedia). Aku <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiivne (ainult meedia). Aku: V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Ühendatud (toetab heli jagamist). Aku <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Kesksugu"</string> <string name="feminine" msgid="1529155595310784757">"Naissugu"</string> <string name="masculine" msgid="4653978041013996303">"Meessugu"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Süsteemivärskendused"</string> </resources> diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml index 91b9d20be307..c330b7684fe5 100644 --- a/packages/SettingsLib/res/values-eu/strings.xml +++ b/packages/SettingsLib/res/values-eu/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Konektatuta (telefonoaren audiorik gabe). Bateria: <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">"Konektatuta (gailuaren audiorik gabe). Bateria: <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">"Konektatuta (telefonoaren edo gailuaren audiorik gabe). Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>."</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktibo. Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktibo. L aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>. R aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktibo. Ezkerreko aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktibo. Eskuineko aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>. R aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Ezkerreko aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Eskuineko aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktibo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gordeta"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktibo (ezkerrekoa soilik)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktibo (eskuinekoa soilik)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktibo (ezkerrekoa eta eskuinekoa)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktibo (multimedia-edukia soilik). Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktibo (multimedia-edukia soilik). L aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>. R aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Konektatuta (audioa partekatzeko eginbidea onartzen du). Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Konektatuta (audioa partekatzeko eginbidea onartzen du). L aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>. R aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Konektatuta (audioa partekatzeko eginbidea onartzen du). Ezkerreko aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Konektatuta (audioa partekatzeko eginbidea onartzen du). Eskuineko aldearen bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Konektatuta (audioa partekatzeko eginbidea onartzen du)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktibo (multimedia-edukia soilik)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Audioa partekatzeko eginbidea onartzen du"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktibo (multimedia-edukia soilik); ezkerreko aldea soilik"</string> @@ -302,16 +285,16 @@ <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Onartu abiarazlea desblokeatzea"</string> <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"OEM desblokeoa onartu nahi duzu?"</string> <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ABISUA: ezarpen hau aktibatuta dagoen bitartean, gailua babesteko eginbideek ez dute gailu honetan funtzionatuko."</string> - <string name="mock_location_app" msgid="6269380172542248304">"Hautatu kokapen faltsuen aplikazioa"</string> + <string name="mock_location_app" msgid="6269380172542248304">"Hautatu asmatutako kokapenen aplikazioa"</string> <string name="mock_location_app_not_set" msgid="6972032787262831155">"Ez da ezarri kokapen faltsuen aplikaziorik"</string> - <string name="mock_location_app_set" msgid="4706722469342913843">"Kokapen faltsuen aplikazioa: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="mock_location_app_set" msgid="4706722469342913843">"Asmatutako kokapenen aplikazioa: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="6829757985772659599">"Sareak"</string> <string name="wifi_display_certification" msgid="1805579519992520381">"Hari gabe bistaratzeko ziurtagiria"</string> <string name="wifi_verbose_logging" msgid="1785910450009679371">"Gaitu wifi-sareetan saioa hasteko modu xehatua"</string> <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> @@ -356,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> @@ -653,7 +636,7 @@ <string name="add_user_failed" msgid="4809887794313944872">"Ezin izan da sortu erabiltzailea"</string> <string name="add_guest_failed" msgid="8074548434469843443">"Ezin izan da sortu beste gonbidatu bat"</string> <string name="user_nickname" msgid="262624187455825083">"Goitizena"</string> - <string name="edit_user_info_message" msgid="6677556031419002895">"Gailua erabiltzen duten guztiek ikusi ahal izango dituzte aukeratu dituzun izena eta irudia."</string> + <string name="edit_user_info_message" msgid="6677556031419002895">"Gailua erabiltzen duten guztiek ikusi ahal izango dituzte aukeratu dituzun izena eta argazkia."</string> <string name="user_add_user" msgid="7876449291500212468">"Gehitu erabiltzaile bat"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatu bat"</string> <string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutroa"</string> <string name="feminine" msgid="1529155595310784757">"Emakumezkoa"</string> <string name="masculine" msgid="4653978041013996303">"Gizonezkoa"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Sistemaren eguneratzeak"</string> </resources> diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml index 7ab13baa7b20..1d331fbf390c 100644 --- a/packages/SettingsLib/res/values-fa/strings.xml +++ b/packages/SettingsLib/res/values-fa/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"فعال"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ذخیرهشده"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"فعال (فقط چپ)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"فعال (فقط راست)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"فعال (چپ و راست)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"فعال (فقط رسانه). باتری: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"فعال (فقط رسانه). باتری چپ: <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_battery_level_lea_support" msgid="5968584103507988820">"متصل (از اشتراک صدا پشتیبانی میکند)، باتری: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"متصل (از اشتراک صدا پشتیبانی میکند)، باتری چپ: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"متصل (از اشتراک صدا پشتیبانی میکند). باتری چپ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"متصل (از اشتراک صدا پشتیبانی میکند). باتری راست: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"متصل (از اشتراک صدا پشتیبانی میکند)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"فعال (فقط رسانه)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"از اشتراک صدا پشتیبانی میکند"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"فعال (فقط رسانه)، فقط چپ"</string> @@ -176,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> انجام نشد."</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"بهدلیل پین یا گذرکلید نادرست، جفتسازی با <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> @@ -253,7 +236,7 @@ <string name="category_personal" msgid="6236798763159385225">"شخصی"</string> <string name="category_work" msgid="4014193632325996115">"کاری"</string> <string name="category_private" msgid="4244892185452788977">"خصوصی"</string> - <string name="category_clone" msgid="1554511758987195974">"مشابهسازی"</string> + <string name="category_clone" msgid="1554511758987195974">"همسانهسازی"</string> <string name="development_settings_title" msgid="140296922921597393">"گزینههای برنامهنویسان"</string> <string name="development_settings_enable" msgid="4285094651288242183">"فعال کردن گزینههای برنامهنویس"</string> <string name="development_settings_summary" msgid="8718917813868735095">"تنظیم گزینههای مربوط به طراحی برنامه"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"خنثی"</string> <string name="feminine" msgid="1529155595310784757">"مؤنث"</string> <string name="masculine" msgid="4653978041013996303">"مذکر"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"بهروزرسانیهای سیستم"</string> </resources> diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml index cafeb1b45951..bdf4301c5c7a 100644 --- a/packages/SettingsLib/res/values-fi/strings.xml +++ b/packages/SettingsLib/res/values-fi/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Yhdistetty (ei puhelimen ääntä), akun varaus <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">"Yhdistetty (ei median ääntä), akun varaus <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">"Yhdistetty (ei puhelimen tai median ääntä), akun varaustaso <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktiivinen. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktiivinen. V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, O: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> virtaa."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktiivinen. V: virtaa <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktiivinen. O: virtaa <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Akun taso <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Akku (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>)"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, O: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> virtaa."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Vasen: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Oikea: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiivinen"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Tallennettu"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiivinen (vain vasen)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktiivinen (vain oikea)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktiivinen (vasen ja oikea)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktiivinen (vain media). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiivinen (vain media). V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, O: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> virtaa."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Yhdistetty (tukee audionjakoa). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Yhdistetty (tukee audionjakoa). V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> virtaa, O: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> virtaa."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Yhdistetty (tukee audionjakoa). Vasen: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Yhdistetty (tukee audionjakoa). Oikea: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> virtaa."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Yhdistetty (tukee audionjakoa)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktiivinen (vain media)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Tukee audionjakoa"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktiivinen (vain media), vain vasen"</string> @@ -176,7 +159,7 @@ <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Peru"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Laiteparin muodostaminen mahdollistaa yhteystietojen ja soittohistorian käyttämisen yhteyden aikana."</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Laiteparin muodostaminen laitteeseen <xliff:g id="DEVICE_NAME">%1$s</xliff:g> epäonnistui."</string> - <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Laiteparia (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ei voitu muodostaa, koska PIN tai avain oli väärä."</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Laiteparia (<xliff:g id="DEVICE_NAME">%1$s</xliff:g>) ei voitu muodostaa, koska PIN tai avainkoodi oli väärä."</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Ei yhteyttä laitteeseen <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Laite <xliff:g id="DEVICE_NAME">%1$s</xliff:g> torjui laitepariyhteyden."</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Tietokone"</string> @@ -610,7 +593,7 @@ <string name="tv_media_transfer_internal_speakers" msgid="8181494402866565865">"Sisäiset kaiuttimet"</string> <string name="profile_connect_timeout_subtext" msgid="4043408193005851761">"Yhteysvirhe. Sammuta laite ja käynnistä se uudelleen."</string> <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Langallinen äänilaite"</string> - <string name="help_label" msgid="3528360748637781274">"Ohje ja palaute"</string> + <string name="help_label" msgid="3528360748637781274">"Ohjeet ja palaute"</string> <string name="storage_category" msgid="2287342585424631813">"Tallennustila"</string> <string name="shared_data_title" msgid="1017034836800864953">"Jaettu data"</string> <string name="shared_data_summary" msgid="5516326713822885652">"Katso ja muokkaa jaettua dataa"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutri"</string> <string name="feminine" msgid="1529155595310784757">"Feminiini"</string> <string name="masculine" msgid="4653978041013996303">"Maskuliini"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Järjestelmäpäivitykset"</string> </resources> diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml index 0eab7c366325..090c3b9613e1 100644 --- a/packages/SettingsLib/res/values-fr-rCA/strings.xml +++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Connecté (aucun téléphone ni média), pile chargée à <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">"Actif. Pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Actif. G. : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D. : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Actif. G : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Actif. D : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Pile : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Pile : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"G. : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D. : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Droite : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Actif"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Enregistré"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Actif (gauche seulement)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Actif (droite seulement)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Actif (gauche et droite)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Actif (contenu multimédia uniquement). Pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Actif (contenu multimédia uniquement). G. : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D. : pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Connecté (prise en charge du partage audio). Pile à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> @@ -240,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Sélectionnez un profil"</string> <string name="category_personal" msgid="6236798763159385225">"Personnel"</string> <string name="category_work" msgid="4014193632325996115">"Professionnel"</string> - <string name="category_private" msgid="4244892185452788977">"Privés"</string> + <string name="category_private" msgid="4244892185452788977">"Privé"</string> <string name="category_clone" msgid="1554511758987195974">"Cloner"</string> <string name="development_settings_title" msgid="140296922921597393">"Options pour les développeurs"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Activer les options pour les développeurs"</string> @@ -290,7 +285,7 @@ <string name="oem_unlock_enable_summary" msgid="5857388174390953829">"Autoriser le déverrouillage du fichier d\'amorce"</string> <string name="confirm_enable_oem_unlock_title" msgid="8249318129774367535">"Permettre le déverrouillage par le fabricant?"</string> <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"AVERTISSEMENT : Les fonctionnalités de protection de l\'appareil ne fonctionneront pas sur cet appareil lorsque ce paramètre est activé."</string> - <string name="mock_location_app" msgid="6269380172542248304">"Sélectionner l\'application de position fictive"</string> + <string name="mock_location_app" msgid="6269380172542248304">"Sélectionner l\'appli de position fictive"</string> <string name="mock_location_app_not_set" msgid="6972032787262831155">"Aucune application de position fictive définie"</string> <string name="mock_location_app_set" msgid="4706722469342913843">"Application de position fictive : <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="6829757985772659599">"Réseautage"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutre"</string> <string name="feminine" msgid="1529155595310784757">"Féminin"</string> <string name="masculine" msgid="4653978041013996303">"Masculin"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Mises à jour du système"</string> </resources> diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml index 1928f32f89bf..be3b05843692 100644 --- a/packages/SettingsLib/res/values-fr/strings.xml +++ b/packages/SettingsLib/res/values-fr/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Connecté (aucun téléphone), batterie à <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">"Connecté (aucun contenu multimédia), batterie à <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">"Connecté (aucun téléphone ni contenu multimédia), batterie à <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Actif. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Actif. Gauche : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batterie, droit : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batterie."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Actif. G : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Actif. D : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batterie (<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>)"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Gauche : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batterie, droit : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batterie."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Gauche : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Droit : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Actif"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Enregistré"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Actif (gauche uniquement)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Actif (droit uniquement)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Actifs (gauche et droit)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Actif (multimédia uniquement). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Actif (multimédia uniquement). Gauche : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batterie, droit : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batterie."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Connecté (compatible avec le partage audio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Connecté (compatible avec le partage audio). Gauche : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batterie, droit : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batterie."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Connecté (compatible avec le partage audio). Gauche : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Connecté (compatible avec le partage audio). Droit : <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batterie."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Connecté (compatible avec le partage audio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Activé (multimédia uniquement)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Compatible avec le partage audio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Activé (multimédia uniquement), gauche uniquement"</string> @@ -249,7 +232,7 @@ <item msgid="4446831566506165093">"350 %"</item> <item msgid="6946761421234586000">"400 %"</item> </string-array> - <string name="choose_profile" msgid="343803890897657450">"Sélectionner un profil"</string> + <string name="choose_profile" msgid="343803890897657450">"Choisissez un profil"</string> <string name="category_personal" msgid="6236798763159385225">"Perso"</string> <string name="category_work" msgid="4014193632325996115">"Pro"</string> <string name="category_private" msgid="4244892185452788977">"Privé"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutre"</string> <string name="feminine" msgid="1529155595310784757">"Féminin"</string> <string name="masculine" msgid="4653978041013996303">"Masculin"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Mises à jour du système"</string> </resources> diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml index d0f47a89173d..21d0bcdef6f9 100644 --- a/packages/SettingsLib/res/values-gl/strings.xml +++ b/packages/SettingsLib/res/values-gl/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Conectado a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (sen teléfono), batería ao <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Conectado a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (sen audio multimedia), batería ao <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Conectado a <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (sen teléfono nin audio multimedia), batería ao <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Activo. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Activo. Esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Dereito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Activo. Esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Activo. Dereito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batería: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Dereito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Dereito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Activo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Gardado"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Activo (só o esquerdo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Activo (só o dereito)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Activos (o esquerdo e o dereito)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Activo (só contido multimedia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Activo (só contido multimedia). Esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Dereito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectado (compatible con audio compartido). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectado (compatible con audio compartido). Esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de batería. Dereito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectado (compatible con audio compartido). Esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectado (compatible con audio compartido). Dereito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de batería."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectado (compatible con audio compartido)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Activo (só contido multimedia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Compatible con audio compartido"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Activo (só contido multimedia), só esquerdo"</string> @@ -176,7 +159,7 @@ <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Cancelar"</string> <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"A vinculación garante acceso aos teus contactos e ao historial de chamadas ao estar conectado"</string> <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Non se puido vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> - <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Non se puido vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>, clave de acceso ou PIN incorrectos."</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Non se puido vincular con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>; clave de acceso ou PIN incorrectos."</string> <string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Non se pode comunicar con <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Vinculación rexeitada por <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string> <string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Ordenador"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutro"</string> <string name="feminine" msgid="1529155595310784757">"Feminino"</string> <string name="masculine" msgid="4653978041013996303">"Masculino"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Actualizacións do sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml index ef53786761e7..35f48e0868b4 100644 --- a/packages/SettingsLib/res/values-gu/strings.xml +++ b/packages/SettingsLib/res/values-gu/strings.xml @@ -96,10 +96,8 @@ <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_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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"જમણી બાજુ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> બૅટરી"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"સક્રિય"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"સાચવેલું"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ચાલુ છે (માત્ર ડાબી બાજુ)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"ચાલુ છે (માત્ર જમણી બાજુ)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"ચાલુ છે (ડાબી અને જમણી બાજુ)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"સક્રિય (માત્ર મીડિયા માટે). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> બૅટરી."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"સક્રિય (માત્ર મીડિયા માટે). ડાબી બાજુ: <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_battery_level_lea_support" msgid="5968584103507988820">"કનેક્ટેડ (ઑડિયો શેરિંગને સપોર્ટ કરે છે). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> બૅટરી."</string> @@ -292,7 +287,7 @@ <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"ચેતવણી: જ્યારે આ સેટિંગ ચાલુ હોય ત્યારે આ ઉપકરણ પર ઉપકરણ સંરક્ષણ સુવિધાઓ કાર્ય કરશે નહીં."</string> <string name="mock_location_app" msgid="6269380172542248304">"મોક લોકેશન ઍપ પસંદ કરો"</string> <string name="mock_location_app_not_set" msgid="6972032787262831155">"કોઈ મોક લોકેશન ઍપ સેટ કરાયેલું નથી"</string> - <string name="mock_location_app_set" msgid="4706722469342913843">"મોક સ્થાન ઍપ્લિકેશન: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="mock_location_app_set" msgid="4706722469342913843">"મૉક લોકેશન ઍપ: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="6829757985772659599">"નેટવર્કિંગ"</string> <string name="wifi_display_certification" msgid="1805579519992520381">"વાયરલેસ ડિસ્પ્લે પ્રમાણન"</string> <string name="wifi_verbose_logging" msgid="1785910450009679371">"વાઇ-ફાઇ વર્બોઝ લૉગિંગ ચાલુ કરો"</string> @@ -601,7 +596,7 @@ <string name="help_label" msgid="3528360748637781274">"સહાય અને પ્રતિસાદ"</string> <string name="storage_category" msgid="2287342585424631813">"સ્ટોરેજ"</string> <string name="shared_data_title" msgid="1017034836800864953">"શેર કરેલો ડેટા"</string> - <string name="shared_data_summary" msgid="5516326713822885652">"શેર કરેલા ડેટાને જુઓ અને તેને સંશોધિત કરો"</string> + <string name="shared_data_summary" msgid="5516326713822885652">"શેર કરેલા ડેટાને જુઓ અને તેમાં ફેરફાર કરો"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"આ વપરાશકર્તા માટે કોઈ શેર કરેલો ડેટા નથી."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"શેર કરેલો ડેટા મેળવવામાં ભૂલ આવી હતી. ફરી પ્રયાસ કરો."</string> <string name="blob_id_text" msgid="8680078988996308061">"શેર કરેલા ડેટાનું ID: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"નાન્યતર"</string> <string name="feminine" msgid="1529155595310784757">"સ્ત્રીલિંગી"</string> <string name="masculine" msgid="4653978041013996303">"પુલ્લિંગી"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"સિસ્ટમ અપડેટ"</string> </resources> diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml index 9499120fedab..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> @@ -287,7 +287,7 @@ <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"चेतावनी: इस सेटिंग के चालू रहने पर डिवाइस सुरक्षा सुविधाएं इस डिवाइस पर काम नहीं करेंगी."</string> <string name="mock_location_app" msgid="6269380172542248304">"मॉक लोकेशन के लिए ऐप्लिकेशन चुनें"</string> <string name="mock_location_app_not_set" msgid="6972032787262831155">"मॉक लोकेशन के लिए ऐप्लिकेशन सेट नहीं है"</string> - <string name="mock_location_app_set" msgid="4706722469342913843">"जगह की दिखावटी जानकारी देने वाला ऐप: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="mock_location_app_set" msgid="4706722469342913843">"मॉक लोकेशन के लिए ऐप्लिकेशन: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="6829757985772659599">"नेटवर्किंग"</string> <string name="wifi_display_certification" msgid="1805579519992520381">"वायरलेस डिसप्ले सर्टिफ़िकेशन"</string> <string name="wifi_verbose_logging" msgid="1785910450009679371">"वाई-फ़ाई वर्बोस लॉगिंग चालू करें"</string> @@ -370,7 +370,7 @@ <string name="debug_hw_drawing_category" msgid="5830815169336975162">"हार्डवेयर ऐक्सेलरेटेड रेंडरिंग"</string> <string name="media_category" msgid="8122076702526144053">"मीडिया"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"निगरानी"</string> - <string name="strict_mode" msgid="889864762140862437">"स्ट्रिक्ट मोड चालू किया गया"</string> + <string name="strict_mode" msgid="889864762140862437">"स्ट्रिक्ट मोड चालू रखें"</string> <string name="strict_mode_summary" msgid="1838248687233554654">"थ्रेड पर लंबा प्रोसेस होने पर स्क्रीन फ़्लैश करें"</string> <string name="pointer_location" msgid="7516929526199520173">"पॉइंटर की जगह"</string> <string name="pointer_location_summary" msgid="957120116989798464">"मौजूदा टच डेटा दिखाने वाला स्क्रीन ओवरले"</string> @@ -721,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"नपुंसक लिंग"</string> <string name="feminine" msgid="1529155595310784757">"महिला"</string> <string name="masculine" msgid="4653978041013996303">"पुरुष"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"सिस्टम अपडेट"</string> </resources> diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml index db2c31845edb..0aba7cd1d9aa 100644 --- a/packages/SettingsLib/res/values-hr/strings.xml +++ b/packages/SettingsLib/res/values-hr/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Povezano (bez telefona), baterija <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">"Povezano (bez medija), baterija <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">"Povezano (bez telefona i medija), baterija <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktivno. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktivno. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktivno. L: baterija na <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktivno. D: baterija na <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> baterije"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Baterija <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Lijeva strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Desna strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktivan"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Spremljeno"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktivno (samo lijevo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktivno (samo desno)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktivno (lijevo i desno)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktivno (samo medijski sadržaji). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktivno (samo medijski sadržaji), L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Povezano (podržava zajedničko slušanje). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Povezano (podržava zajedničko slušanje), L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> baterije."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Povezano (podržava zajedničko slušanje). Lijeva strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Povezano (podržava zajedničko slušanje). Desna strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterije."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Povezano (podržava zajedničko slušanje)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktivno (samo medijski sadržaji)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Podržava zajedničko slušanje"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktivno (samo medijski sadržaji), samo lijeva"</string> @@ -406,7 +389,7 @@ <string name="disable_overlays_summary" msgid="1954852414363338166">"Uvijek se koristi GPU za slaganje zaslona"</string> <string name="simulate_color_space" msgid="1206503300335835151">"Simulacija prostora boja"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Omogući OpenGL praćenja"</string> - <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Onemogući USB audiousmj."</string> + <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Onemogući USB audiousmjeravanje"</string> <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Onemogućuje se automatsko usmjeravanje na USB audioperiferiju"</string> <string name="debug_layout" msgid="1659216803043339741">"Prikaži okvir prikaza"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"Prikazuju se obrubi, margine itd. isječaka"</string> @@ -613,7 +596,7 @@ <string name="help_label" msgid="3528360748637781274">"Pomoć i povratne informacije"</string> <string name="storage_category" msgid="2287342585424631813">"Pohrana"</string> <string name="shared_data_title" msgid="1017034836800864953">"Dijeljeni podaci"</string> - <string name="shared_data_summary" msgid="5516326713822885652">"Prikaz i izmjena dijeljenih podataka"</string> + <string name="shared_data_summary" msgid="5516326713822885652">"Pogledajte i izmjenite dijeljene podatke"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Nema dijeljenih podataka za ovog korisnika."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Došlo je do pogreške prilikom dohvaćanja dijeljenih podataka. Pokušajte ponovno."</string> <string name="blob_id_text" msgid="8680078988996308061">"ID dijeljenih podataka: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Srednji rod"</string> <string name="feminine" msgid="1529155595310784757">"Ženski rod"</string> <string name="masculine" msgid="4653978041013996303">"Muški rod"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Ažuriranja sustava"</string> </resources> diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml index 207e84af968c..e01c20aa4e32 100644 --- a/packages/SettingsLib/res/values-hu/strings.xml +++ b/packages/SettingsLib/res/values-hu/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Csatlakoztatva (telefonhang nélkül); az akkumulátor töltöttségi szintje: <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">"Csatlakoztatva (médiahang nélkül); az akkumulátor töltöttségi szintje: <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">"Csatlakoztatva (nincs telefon- és médiahang); az akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktív. Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktív. Akkumulátorok töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (bal) és <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (jobb)."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktív. Akku: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (bal)."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktív. Akku: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (jobb)."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Akkumulátor: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Akkumulátor: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Akkumulátorok töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (bal) és <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (jobb)."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (bal)."</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (jobb)."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktív"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Mentve"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktív (csak bal)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktív (csak jobb)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktív (bal és jobb)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktív (csak médiatartalom lejátszása esetén). Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktív (csak médiatartalom lejátszása esetén). Akkumulátorok töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (bal) és <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (jobb)."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Csatlakoztatva (támogatja a hang megosztását). Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Csatlakoztatva (támogatja a hang megosztását). Akkumulátorok töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (bal) és <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (jobb)."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Csatlakoztatva (támogatja a hang megosztását). Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (bal)."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Csatlakoztatva (támogatja a hang megosztását). Akkumulátor töltöttségi szintje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (jobb)."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Csatlakoztatva (támogatja a hang megosztását)."</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktív (csak médiatartalom lejátszása)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Támogatja a hang megosztását"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktív (csak médiatartalom lejátszása), csak a bal"</string> @@ -406,7 +389,7 @@ <string name="disable_overlays_summary" msgid="1954852414363338166">"Mindig a GPU használata képernyő-feldolgozáshoz"</string> <string name="simulate_color_space" msgid="1206503300335835151">"Színtérszimuláció"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"OpenGL nyomon követése"</string> - <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Hangátirányítás tiltása"</string> + <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Hangátirányítás tiltása - USB"</string> <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Hangátirányítás tiltása az USB-s hangeszközöknél"</string> <string name="debug_layout" msgid="1659216803043339741">"Elrendezéshatárok"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"Kliphatárok, margók stb. megjelenítése."</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Semleges nemű alak"</string> <string name="feminine" msgid="1529155595310784757">"Női"</string> <string name="masculine" msgid="4653978041013996303">"Férfi"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Rendszerfrissítések"</string> </resources> diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml index 1b8b23676839..5e3d779c34a2 100644 --- a/packages/SettingsLib/res/values-hy/strings.xml +++ b/packages/SettingsLib/res/values-hy/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Ձախ ականջակալի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Աջ ականջակալի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ակտիվ է"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Պահված է"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ակտիվ է (միայն ձախ)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Ակտիվ է (միայն աջ)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ակտիվ է (ձախ և աջ)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ակտիվ է (միայն մեդիա)։ Մարտկոցի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>։"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Ակտիվ է (միայն մեդիա)։ Ձախ ականջակալի լիցքը՝ <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_battery_level_lea_support" msgid="5968584103507988820">"Միացված է (աջակցում է աուդիոյի փոխանցում)։ Մարտկոցի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>։"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Միացված է (աջակցում է աուդիոյի փոխանցում)։ Ձախ ականջակալի լիցքը՝ <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Միացված է (աջակցում է աուդիոյի փոխանցում)։ Ձախ ականջակալի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>։"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Միացված է (աջակցում է աուդիոյի փոխանցում)։ Աջ ականջակալի լիցքը՝ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>։"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Միացված է (աջակցում է աուդիոյի փոխանցում)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ակտիվ է (միայն մեդիա)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Աջակցում է աուդիոյի փոխանցում"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ակտիվ է (միայն մեդիա), միայն ձախ"</string> @@ -176,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> @@ -612,8 +595,8 @@ <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Լարով աուդիո սարք"</string> <string name="help_label" msgid="3528360748637781274">"Օգնություն և հետադարձ կապ"</string> <string name="storage_category" msgid="2287342585424631813">"Տարածք"</string> - <string name="shared_data_title" msgid="1017034836800864953">"Հասանելի դարձված տվյալներ"</string> - <string name="shared_data_summary" msgid="5516326713822885652">"Դիտեք և փոփոխեք հասանելի դարձված տվյալները"</string> + <string name="shared_data_title" msgid="1017034836800864953">"Ընդհանուր տվյալներ"</string> + <string name="shared_data_summary" msgid="5516326713822885652">"Դիտեք և փոփոխեք ընդհանուր տվյալները"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Այս օգտատիրոջ համար ընդհանուր տվյալներ չկան։"</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Չհաջողվեց բեռնել ընդհանուր տվյալները։ Նորից փորձեք։"</string> <string name="blob_id_text" msgid="8680078988996308061">"Հասանելի դարձված տվյալների ID՝ <xliff:g id="BLOB_ID">%d</xliff:g>"</string> @@ -623,7 +606,7 @@ <string name="accessor_info_title" msgid="8289823651512477787">"Տվյալներով կիսվող հավելվածներ"</string> <string name="accessor_no_description_text" msgid="7510967452505591456">"Հավելվածի կողմից տրամադրված նկարագրություն չկա։"</string> <string name="accessor_expires_text" msgid="4625619273236786252">"Վարձակալության ժակետն ավարտվում է՝ <xliff:g id="DATE">%s</xliff:g>"</string> - <string name="delete_blob_text" msgid="2819192607255625697">"Ջնջել հասանելի դարձված տվյալները"</string> + <string name="delete_blob_text" msgid="2819192607255625697">"Ջնջել ընդհանուր տվյալները"</string> <string name="delete_blob_confirmation_text" msgid="7807446938920827280">"Իսկապե՞ս ուզում եք ջնջել հասանելի դարձված այս տվյալները։"</string> <string name="user_add_user_item_summary" msgid="5748424612724703400">"Օգտվողներն իրենց անձնական հավելվածներն ու բովանդակությունն ունեն"</string> <string name="user_add_profile_item_summary" msgid="5418602404308968028">"Դուք կարող եք սահմանափակել մուտքի իրավունքը ծրագրեր և ձեր հաշվի բովանդակություն:"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Չեզոք"</string> <string name="feminine" msgid="1529155595310784757">"Իգական"</string> <string name="masculine" msgid="4653978041013996303">"Արական"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Համակարգի թարմացումներ"</string> </resources> diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml index d9730b39f55a..79fb4dab8e20 100644 --- a/packages/SettingsLib/res/values-in/strings.xml +++ b/packages/SettingsLib/res/values-in/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Terhubung (tanpa ponsel), baterai <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">"Terhubung (tanpa media), baterai <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">"Terhubung (tanpa ponsel atau media), baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktif. Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktif. Baterai 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>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktif. L: Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktif. R: Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Baterai 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>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Kiri: Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Kanan: Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktif"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Disimpan"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktif (hanya kiri)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktif (hanya kanan)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktif (kiri dan kanan)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktif (hanya media). Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktif (hanya media). Baterai 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>."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Terhubung (mendukung berbagi audio). Baterai<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Terhubung (mendukung berbagi audio). Baterai 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>."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Terhubung (mendukung berbagi audio). Kiri: Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Terhubung (mendukung berbagi audio). Kanan: Baterai <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Terhubung (mendukung berbagi audio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktif (hanya media)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Mendukung berbagi audio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktif (hanya media), hanya kiri"</string> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string> <string name="category_personal" msgid="6236798763159385225">"Pribadi"</string> <string name="category_work" msgid="4014193632325996115">"Kerja"</string> - <string name="category_private" msgid="4244892185452788977">"Pribadi"</string> + <string name="category_private" msgid="4244892185452788977">"Privasi"</string> <string name="category_clone" msgid="1554511758987195974">"Clone"</string> <string name="development_settings_title" msgid="140296922921597393">"Opsi developer"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Aktifkan opsi developer"</string> @@ -386,9 +369,9 @@ <string name="debug_drawing_category" msgid="5066171112313666619">"Gambar"</string> <string name="debug_hw_drawing_category" msgid="5830815169336975162">"Render yang dipercepat hardware"</string> <string name="media_category" msgid="8122076702526144053">"Media"</string> - <string name="debug_monitoring_category" msgid="1597387133765424994">"Memantau"</string> + <string name="debug_monitoring_category" msgid="1597387133765424994">"Pemantauan"</string> <string name="strict_mode" msgid="889864762140862437">"Mode ketat diaktifkan"</string> - <string name="strict_mode_summary" msgid="1838248687233554654">"Buat layar berkedip saat aplikasi berjalan lama di thread utama"</string> + <string name="strict_mode_summary" msgid="1838248687233554654">"Buat layar berkedip saat aplikasi berlama-lama menjalankan operasi di thread utama"</string> <string name="pointer_location" msgid="7516929526199520173">"Lokasi kursor"</string> <string name="pointer_location_summary" msgid="957120116989798464">"Overlay layar menampilkan data sentuhan saat ini"</string> <string name="show_touches" msgid="8437666942161289025">"Tampilkan ketukan"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Netral"</string> <string name="feminine" msgid="1529155595310784757">"Feminin"</string> <string name="masculine" msgid="4653978041013996303">"Maskulin"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Update Sistem"</string> </resources> diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml index 60370f594dea..dfb780dd0a62 100644 --- a/packages/SettingsLib/res/values-is/strings.xml +++ b/packages/SettingsLib/res/values-is/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Tengt (enginn sími), staða rafhlöðu <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">"Tengt (ekkert efni), staða rafhlöðu <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">"Tengt (enginn sími eða efni), staða rafhlöðu <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Virkt. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Virkt. V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Kveikt. V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> hleðsla."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Kveikt. H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> hleðsla."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Rafhlaða <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Vinstri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Hægri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Virkt"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Vistað"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Kveikt (eingöngu vinstra)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Kveikt (eingöngu hægra)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Kveikt (vinstra og hægra)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Virkt (eingöngu margmiðlunarefni). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Virkt (eingöngu margmiðlunarefni), V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Tengt (styður hljóðdeilingu), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Tengt (styður hljóðdeilingu), V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Tengt (styður hljóðdeilingu). Vinstri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Tengt (styður hljóðdeilingu). Hægri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> rafhlöðuhleðsla."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Tengt (styður hljóðdeilingu)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Virkt (eingöngu margmiðlunarefni)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Styður hljóðdeilingu"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Virkt (eingöngu margmiðlunarefni), eingöngu vinstri"</string> @@ -196,7 +179,7 @@ <string name="accessibility_wifi_other_device" msgid="2815627624555795918">"Tengt við tækið þitt."</string> <string name="accessibility_wifi_security_type_none" msgid="162352241518066966">"Opið net"</string> <string name="accessibility_wifi_security_type_secured" msgid="2399774097343238942">"Öruggt net"</string> - <string name="process_kernel_label" msgid="950292573930336765">"Android stýrikerfið"</string> + <string name="process_kernel_label" msgid="950292573930336765">"Android-stýrikerfið"</string> <string name="data_usage_uninstalled_apps" msgid="1933665711856171491">"Fjarlægð forrit"</string> <string name="data_usage_uninstalled_apps_users" msgid="5533981546921913295">"Fjarlægð forrit og notendur"</string> <string name="data_usage_ota" msgid="7984667793701597001">"Kerfisuppfærslur"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Kynsegin"</string> <string name="feminine" msgid="1529155595310784757">"Kvenkyn"</string> <string name="masculine" msgid="4653978041013996303">"Karlkyn"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Kerfisuppfærslur"</string> </resources> diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml index fbae08d37e63..26b9c64ca118 100644 --- a/packages/SettingsLib/res/values-it/strings.xml +++ b/packages/SettingsLib/res/values-it/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> connesso (telefono escluso), batteria al <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> connesso (contenuti multimediali esclusi), batteria al <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> connesso (telefono o contenuti multimediali esclusi), batteria al <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Attivo. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Attivo. S: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> di batteria. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> di batteria."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Attivo. S: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Attivo. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Batteria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batteria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"S: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> di batteria. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> di batteria."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Sinistro: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Destro: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Attivo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Dispositivo salvato"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Attivo (solo sinistro)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Attivo (solo destro)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Attivi (destro e sinistro)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Attivo (solo contenuti multimediali). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Attivo (solo contenuti multimediali). S: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> di batteria. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> di batteria."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Connesso (supporta la condivisione audio). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Connesso (supporta la condivisione audio). S: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> di batteria. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> di batteria."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Connesso (supporta la condivisione audio). Sinistro: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Connesso (supporta la condivisione audio). Destro: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> di batteria."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Connesso (supporta la condivisione audio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Attivo (solo contenuti multimediali)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Supporta la condivisione audio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Attivo (solo contenuti multimediali), solo sinistro"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutro"</string> <string name="feminine" msgid="1529155595310784757">"Femminile"</string> <string name="masculine" msgid="4653978041013996303">"Maschile"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Aggiornamenti di sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml index 5a6c75162fe3..c232396875fd 100644 --- a/packages/SettingsLib/res/values-iw/strings.xml +++ b/packages/SettingsLib/res/values-iw/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"סוללה בצד שמאל: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"סוללה בצד ימין: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"פעיל"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"בוצעה שמירה"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"פעיל (שמאל בלבד)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"פעיל (ימין בלבד)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"פעיל (ימין ושמאל)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"פעיל (מדיה בלבד). סוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"פעיל (מדיה בלבד). סוללה בצד שמאל: <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_battery_level_lea_support" msgid="5968584103507988820">"מחובר (תמיכה בשיתוף אודיו). סוללה: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"מחובר (תמיכה בשיתוף אודיו). סוללה בצד שמאל: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"מחובר (תמיכה בשיתוף אודיו). סוללה בצד שמאל: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"מחובר (תמיכה בשיתוף אודיו). סוללה בצד ימין: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"מחובר (תמיכה בשיתוף אודיו)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"פעיל (מדיה בלבד)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"תמיכה בשיתוף אודיו"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"פעיל (מדיה בלבד), שמאל בלבד"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ניטרלי"</string> <string name="feminine" msgid="1529155595310784757">"נקבה"</string> <string name="masculine" msgid="4653978041013996303">"זכר"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"עדכוני מערכת"</string> </resources> diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml index 26bf76e0da6e..4d2f8fc8a809 100644 --- a/packages/SettingsLib/res/values-ja/strings.xml +++ b/packages/SettingsLib/res/values-ja/strings.xml @@ -96,10 +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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"右: バッテリー残量 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"有効"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"保存済み"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"アクティブ(左のみ)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"アクティブ(右のみ)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"アクティブ(左と右)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"有効(メディアのみ)。バッテリー残量 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"有効(メディアのみ)。左: バッテリー残量 <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_battery_level_lea_support" msgid="5968584103507988820">"接続済み(音声の共有をサポート)。バッテリー残量 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> @@ -240,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"プロファイルの選択"</string> <string name="category_personal" msgid="6236798763159385225">"個人用"</string> <string name="category_work" msgid="4014193632325996115">"仕事用"</string> - <string name="category_private" msgid="4244892185452788977">"非公開"</string> + <string name="category_private" msgid="4244892185452788977">"プライベート"</string> <string name="category_clone" msgid="1554511758987195974">"クローン"</string> <string name="development_settings_title" msgid="140296922921597393">"開発者向けオプション"</string> <string name="development_settings_enable" msgid="4285094651288242183">"開発者向けオプションの有効化"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"中性"</string> <string name="feminine" msgid="1529155595310784757">"女性"</string> <string name="masculine" msgid="4653978041013996303">"男性"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"システム アップデート"</string> </resources> diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml index 266a5574ecfa..4ade50f7de9a 100644 --- a/packages/SettingsLib/res/values-ka/strings.xml +++ b/packages/SettingsLib/res/values-ka/strings.xml @@ -98,10 +98,8 @@ <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> <skip /> <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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -109,12 +107,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"მარჯვენა: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ბატარეა"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"აქტიური"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"შენახული"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"აქტიური (მხოლოდ მარცხენა)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"აქტიური (მხოლოდ მარჯვენა)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"აქტიური (მარცხენა და მარჯვენა)"</string> <!-- String.format failed for translation --> <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> <skip /> @@ -244,7 +239,7 @@ <string name="choose_profile" msgid="343803890897657450">"აირჩიეთ პროფილი"</string> <string name="category_personal" msgid="6236798763159385225">"პირადი"</string> <string name="category_work" msgid="4014193632325996115">"სამსახური"</string> - <string name="category_private" msgid="4244892185452788977">"პირადი"</string> + <string name="category_private" msgid="4244892185452788977">"კერძო"</string> <string name="category_clone" msgid="1554511758987195974">"კლონის შექმნა"</string> <string name="development_settings_title" msgid="140296922921597393">"პარამეტრები დეველოპერებისთვის"</string> <string name="development_settings_enable" msgid="4285094651288242183">"დეველოპერთა პარამეტრების ჩართვა"</string> @@ -730,4 +725,5 @@ <string name="neuter" msgid="2075249330106127310">"საშუალო"</string> <string name="feminine" msgid="1529155595310784757">"მდედრობითი"</string> <string name="masculine" msgid="4653978041013996303">"მამრობითი"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"სისტემური განახლებები"</string> </resources> diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml index eee0ac6fc56d..4e034b456f50 100644 --- a/packages/SettingsLib/res/values-kk/strings.xml +++ b/packages/SettingsLib/res/values-kk/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Сол жақ: батарея зарядының деңгейі – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Оң жақ: батарея зарядының деңгейі – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Қосулы"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Сақталған"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Істеп тұр (тек сол жағы)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Істеп тұр (тек оң жағы)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Істеп тұр (екі жағы да)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Істеп тұр (тек мультимедиа). Батарея зарядының деңгейі – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Істеп тұр (тек мультимедиа). Сол жақ: батарея зарядының деңгейі — <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_battery_level_lea_support" msgid="5968584103507988820">"Қосылды (аудио бөлісуге мүмкіндік береді). Батарея зарядының деңгейі:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Қосылды (аудио бөлісуге мүмкіндік береді). Сол жақ: батарея зарядының деңгейі — <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Қосылды (аудио бөлісуге мүмкіндік береді). Сол жақ: батарея зарядының деңгейі – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Қосылды (аудио бөлісуге мүмкіндік береді). Оң жақ: батарея зарядының деңгейі – <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Қосылды (аудио бөлісуге мүмкіндік береді)."</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Істеп тұр (тек мультимедиа)."</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Аудио бөлісуге мүмкіндік береді."</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Тек сол жақ істеп тұр (мультимедиа ғана)."</string> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Профильді таңдау"</string> <string name="category_personal" msgid="6236798763159385225">"Жеке"</string> <string name="category_work" msgid="4014193632325996115">"Жұмыс"</string> - <string name="category_private" msgid="4244892185452788977">"Жеке"</string> + <string name="category_private" msgid="4244892185452788977">"Құпия"</string> <string name="category_clone" msgid="1554511758987195974">"Клондау"</string> <string name="development_settings_title" msgid="140296922921597393">"Әзірлеуші опциялары"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Әзірлеуші параметрлерін қосу"</string> @@ -388,7 +371,7 @@ <string name="media_category" msgid="8122076702526144053">"Mультимeдиа"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"Бақылау"</string> <string name="strict_mode" msgid="889864762140862437">"Қатаң режим қосылған"</string> - <string name="strict_mode_summary" msgid="1838248687233554654">"Қолданбалар негізгі жолда ұзақ әрекеттерді орындағанда экранды жыпылықтату"</string> + <string name="strict_mode_summary" msgid="1838248687233554654">"Қолданбалар ұзақ операцияларды орындағанда экранды жыпылықтату"</string> <string name="pointer_location" msgid="7516929526199520173">"Меңзер орны"</string> <string name="pointer_location_summary" msgid="957120116989798464">"Экран бетіне түртілген элемент дерегі көрсетіледі"</string> <string name="show_touches" msgid="8437666942161289025">"Түрту қимылын көрсету"</string> @@ -407,7 +390,7 @@ <string name="simulate_color_space" msgid="1206503300335835151">"Түстер кеңістігінің симуляциясы"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"OpenGL трейстерін қосу"</string> <string name="usb_audio_disable_routing" msgid="3367656923544254975">"USB-мен аудио жіберуді өшіру"</string> - <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Сыртқы USB аудио құрылғыларына автоматты жіберуді өшіру"</string> + <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Сыртқы USB аудио құрылғыларына автоматты түрде жіберуді өшіру"</string> <string name="debug_layout" msgid="1659216803043339741">"Жиектерді көрсету"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"Қию шегін, шеттерді, т.б. көрсету"</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Оңнан солға орналастыру"</string> @@ -612,7 +595,7 @@ <string name="media_transfer_wired_device_name" msgid="4447880899964056007">"Сымды аудио құрылғысы"</string> <string name="help_label" msgid="3528360748637781274">"Анықтама және пікір"</string> <string name="storage_category" msgid="2287342585424631813">"Жад"</string> - <string name="shared_data_title" msgid="1017034836800864953">"Бөліскен дерек"</string> + <string name="shared_data_title" msgid="1017034836800864953">"Ортақ дерек"</string> <string name="shared_data_summary" msgid="5516326713822885652">"Ортақ деректерді көру және өзгерту"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Бұл пайдаланушымен бөліскен дерек жоқ."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Ортақ деректер алу кезінде қате шықты. Қайталап көріңіз."</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Орта тек"</string> <string name="feminine" msgid="1529155595310784757">"Аналық тек"</string> <string name="masculine" msgid="4653978041013996303">"Аталық тек"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Жүйенің жаңартылған нұсқалары"</string> </resources> diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml index 2ba422be5678..60f88ead0795 100644 --- a/packages/SettingsLib/res/values-km/strings.xml +++ b/packages/SettingsLib/res/values-km/strings.xml @@ -721,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"អភេទ"</string> <string name="feminine" msgid="1529155595310784757">"ស្រី"</string> <string name="masculine" msgid="4653978041013996303">"ប្រុស"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"បច្ចុប្បន្នភាពប្រព័ន្ធ"</string> </resources> diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml index 64facffcbf37..6594b5e7a38a 100644 --- a/packages/SettingsLib/res/values-kn/strings.xml +++ b/packages/SettingsLib/res/values-kn/strings.xml @@ -96,10 +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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ಬಲ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ಬ್ಯಾಟರಿ ಮಟ್ಟ"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ಸಕ್ರಿಯ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ಸಕ್ರಿಯವಾಗಿದೆ (ಎಡಕಿವಿಯ ಸಾಧನ ಮಾತ್ರ)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"ಸಕ್ರಿಯವಾಗಿದೆ (ಬಲಕಿವಿಯ ಸಾಧನ ಮಾತ್ರ)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"ಸಕ್ರಿಯವಾಗಿವೆ (ಎಡ ಮತ್ತು ಬಲಕಿವಿಯ ಸಾಧನಗಳು)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"ಸಕ್ರಿಯವಾಗಿದೆ (ಮೀಡಿಯಾ ಮಾತ್ರ). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ಬ್ಯಾಟರಿ ಮಟ್ಟ."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"ಸಕ್ರಿಯವಾಗಿದೆ (ಮೀಡಿಯಾ ಮಾತ್ರ). ಎಡ: <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_battery_level_lea_support" msgid="5968584103507988820">"ಕನೆಕ್ಟ್ ಆಗಿದೆ (ಆಡಿಯೋ ಹಂಚಿಕೊಳ್ಳುವಿಕೆಯನ್ನು ಬೆಂಬಲಿಸುತ್ತದೆ). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ಬ್ಯಾಟರಿ ಮಟ್ಟ."</string> @@ -395,7 +390,7 @@ <string name="simulate_color_space" msgid="1206503300335835151">"ಬಣ್ಣದ ಸ್ಥಳ ಸಿಮ್ಯುಲೇಟ್"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"OpenGL ಕುರುಹುಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string> <string name="usb_audio_disable_routing" msgid="3367656923544254975">"USB ಆಡಿಯೋ ರೂಟಿಂಗ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string> - <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"USB ಆಡಿಯೋ ಸಲಕರಣೆಗಳಿಗೆ ಸ್ವಯಂ ರೂಟಿಂಗ್ ನಿಷ್ಕ್ರಿಯ."</string> + <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"USB ಆಡಿಯೋ ಸಲಕರಣೆಗಳಿಗೆ ಸ್ವಯಂಚಾಲಿತ ರೂಟಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ."</string> <string name="debug_layout" msgid="1659216803043339741">"ಲೇಔಟ್ ಪರಿಮಿತಿಗಳನ್ನು ತೋರಿಸು"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"ಕ್ಲಿಪ್ನ ಗಡಿಗಳು, ಅಂಚುಗಳು, ಇತ್ಯಾದಿ ತೋರಿಸು."</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"RTL ಲೇಔಟ್ ಡೈರೆಕ್ಷನ್ ಫೋರ್ಸ್ ಮಾಡುವಿಕೆ"</string> @@ -406,7 +401,7 @@ <string name="force_msaa" msgid="4081288296137775550">"4x MSAA ಫೋರ್ಸ್ ಮಾಡಿ"</string> <string name="force_msaa_summary" msgid="9070437493586769500">"OpenGL ES 2.0 ಅಪ್ಲಿಕೇಶನ್ಗಳಲ್ಲಿ 4x MSAA ಸಕ್ರಿಯಗೊಳಿಸಿ"</string> <string name="show_non_rect_clip" msgid="7499758654867881817">"ಆಯತಾಕಾರವಲ್ಲದ ಕ್ಲಿಪ್ ಕಾರ್ಯಾಚರಣೆ ಡೀಬಗ್"</string> - <string name="track_frame_time" msgid="522674651937771106">"ಪ್ರೊಫೈಲ್ HWUI ಸಲ್ಲಿಸಲಾಗುತ್ತಿದೆ"</string> + <string name="track_frame_time" msgid="522674651937771106">"ಪ್ರೊಫೈಲ್ HWUI ರೆಂಡರಿಂಗ್"</string> <string name="enable_gpu_debug_layers" msgid="4986675516188740397">"GPU ಡೀಬಗ್ ಲೇಯರ್ಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string> <string name="enable_gpu_debug_layers_summary" msgid="4921521407377170481">"ಡೀಬಗ್ ಅಪ್ಲಿಕೇಶನ್ಗಳಿಗಾಗಿ GPU ಡೀಬಗ್ ಲೇಯರ್ಗಳನ್ನು ಲೋಡ್ ಮಾಡುವುದನ್ನು ಅನುಮತಿಸಿ"</string> <string name="enable_verbose_vendor_logging" msgid="1196698788267682072">"ವರ್ಬೋಸ್ ವೆಂಡರ್ ಲಾಗಿಂಗ್ ಸಕ್ರಿಯಗೊಳಿಸಿ"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ನಪುಂಸಕ"</string> <string name="feminine" msgid="1529155595310784757">"ಮಹಿಳೆಯರಿಗಾಗಿ"</string> <string name="masculine" msgid="4653978041013996303">"ಪುರುಷರಿಗಾಗಿ"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"ಸಿಸ್ಟಂ ಅಪ್ಡೇಟ್ಗಳು"</string> </resources> diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml index b6988ce418d9..d93af73c6a56 100644 --- a/packages/SettingsLib/res/values-ko/strings.xml +++ b/packages/SettingsLib/res/values-ko/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"왼쪽 배터리는 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>입니다."</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"오른쪽 배터리는 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>입니다."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"활성"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"저장됨"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"활성(왼쪽만)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"활성(오른쪽만)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"활성(왼쪽 및 오른쪽)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"사용 중입니다(미디어 전용). 배터리는 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>입니다."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"사용 중입니다(미디어 전용). 배터리는 왼쪽 <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_battery_level_lea_support" msgid="5968584103507988820">"연결되었습니다(오디오 공유 지원). 배터리는 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>입니다."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"연결되었습니다(오디오 공유 지원). 배터리는 왼쪽 <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"연결되었습니다(오디오 공유 지원). 왼쪽 배터리는 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>입니다."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"연결되었습니다(오디오 공유 지원). 오른쪽 배터리는 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>입니다."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"연결되었습니다(오디오 공유 지원)."</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"사용 중(미디어 전용)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"오디오 공유 지원"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"사용 중(미디어 전용), 왼쪽만"</string> @@ -304,7 +287,7 @@ <string name="confirm_enable_oem_unlock_text" msgid="854131050791011970">"경고: 이 설정을 사용하는 동안에는 이 기기에서 기기 보호 기능이 작동하지 않습니다."</string> <string name="mock_location_app" msgid="6269380172542248304">"가상 위치 앱 선택"</string> <string name="mock_location_app_not_set" msgid="6972032787262831155">"가상 위치 앱이 설정되어 있지 않음"</string> - <string name="mock_location_app_set" msgid="4706722469342913843">"모의 위치 앱: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> + <string name="mock_location_app_set" msgid="4706722469342913843">"가상 위치 앱: <xliff:g id="APP_NAME">%1$s</xliff:g>"</string> <string name="debug_networking_category" msgid="6829757985772659599">"네트워크"</string> <string name="wifi_display_certification" msgid="1805579519992520381">"무선 디스플레이 인증서"</string> <string name="wifi_verbose_logging" msgid="1785910450009679371">"Wi-Fi 상세 로깅 사용"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"중성"</string> <string name="feminine" msgid="1529155595310784757">"여성"</string> <string name="masculine" msgid="4653978041013996303">"남성"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"시스템 업데이트"</string> </resources> diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml index d084a9bf79c8..4a76d5500e23 100644 --- a/packages/SettingsLib/res/values-ky/strings.xml +++ b/packages/SettingsLib/res/values-ky/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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">"Жигердүү. Батарея: 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>."</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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Батарея: 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>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Сол кулак – батареянын деңгээли: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Оң кулак – батареянын деңгээли: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Жигердүү"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Сакталган"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Иштеп жатат (сол тарап гана)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Иштеп жатат (оң тарап гана)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Иштеп жатат (сол жана оң)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Жигердүү (медиа үчүн гана). Батарея: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Жигердүү (медиа үчүн гана). Батарея: 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>."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Туташкан (чогуу угуу колдоого алынат). Батарея: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Туташкан (чогуу угуу колдоого алынат). Батарея: 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>."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Туташкан (чогуу угуу колдоого алынат). Сол кулак – батареянын деңгээли: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Туташкан (чогуу угуу колдоого алынат). Оң кулак – батареянын деңгээли: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Туташкан (чогуу угуу колдоого алынат)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Активдүү (медиа үчүн гана)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Чогуу угуу колдоого алынат"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Активдүү (медиа үчүн гана), сол кулакчын гана"</string> @@ -653,7 +636,7 @@ <string name="add_user_failed" msgid="4809887794313944872">"Жаңы колдонуучу түзүлбөй калды"</string> <string name="add_guest_failed" msgid="8074548434469843443">"Жаңы конок түзүлгөн жок"</string> <string name="user_nickname" msgid="262624187455825083">"Ылакап аты"</string> - <string name="edit_user_info_message" msgid="6677556031419002895">"Сиз тандаган аталыш жана сүрөт ушул түзмөктү колдонгондордун баарына көрүнөт."</string> + <string name="edit_user_info_message" msgid="6677556031419002895">"Сиз тандаган ысым жана сүрөт ушул түзмөктү колдонгондордун баарына көрүнөт."</string> <string name="user_add_user" msgid="7876449291500212468">"Колдонуучу кошуу"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Конок кошуу"</string> <string name="guest_exit_guest" msgid="5908239569510734136">"Конокту өчүрүү"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Орто жак"</string> <string name="feminine" msgid="1529155595310784757">"Аял"</string> <string name="masculine" msgid="4653978041013996303">"Эркек"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Тутум жаңыртуулары"</string> </resources> diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml index cb0c5da17593..a6040ef0b434 100644 --- a/packages/SettingsLib/res/values-lo/strings.xml +++ b/packages/SettingsLib/res/values-lo/strings.xml @@ -371,7 +371,7 @@ <string name="media_category" msgid="8122076702526144053">"ມີເດຍ"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"ກຳລັງກວດສອບ"</string> <string name="strict_mode" msgid="889864762140862437">"ເປີດໃຊ້ໂໝດເຄັ່ງຄັດ"</string> - <string name="strict_mode_summary" msgid="1838248687233554654">"ກະພິບໜ້າຈໍເມື່ອມີແອັບ ເຮັດວຽກດົນເກີນໄປໃນເທຣດຫຼັກ"</string> + <string name="strict_mode_summary" msgid="1838248687233554654">"ກະພິບໜ້າຈໍເມື່ອມີແອັບເຮັດວຽກດົນເກີນໄປໃນເທຣດຫຼັກ"</string> <string name="pointer_location" msgid="7516929526199520173">"ຕຳແໜ່ງໂຕຊີ້"</string> <string name="pointer_location_summary" msgid="957120116989798464">"ການວາງຊ້ອນໜ້າຈໍກຳລັງສະແດງຂໍ້ມູນການສຳຜັດໃນປັດຈຸບັນ"</string> <string name="show_touches" msgid="8437666942161289025">"ສະແດງການແຕະ"</string> @@ -721,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ບໍ່ມີເພດ"</string> <string name="feminine" msgid="1529155595310784757">"ເພດຍິງ"</string> <string name="masculine" msgid="4653978041013996303">"ເພດຊາຍ"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"ການອັບເດດລະບົບ"</string> </resources> diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml index 0d97309bddbb..b84cefb598f7 100644 --- a/packages/SettingsLib/res/values-lt/strings.xml +++ b/packages/SettingsLib/res/values-lt/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Prisijungta (<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>), (telefono ar medijos nėra), akumuliatoriaus įkrovos lygis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktyvus. Akumuliatorius lygis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktyvus. Akumuliatoriaus lygis kairėje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, dešinėje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktyvus. K.: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> akumuliatoriaus įkrova."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktyvus. D.: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> akumuliatoriaus įkrova."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Akumuliatoriaus įkrova: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Akumuliatorius: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Akumuliatoriaus lygis kairėje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, dešinėje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Akumuliatoriaus lygis dešinėje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktyvus"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Išsaugota"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktyvus (tik kairiojoje pusėje)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktyvus (tik dešiniojoje pusėje)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktyvus (kairiojoje ir dešiniojoje pusėse)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktyvus (tik medija). Akumuliatorius lygis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktyvus (tik medija), akumuliatoriaus lygis kairėje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, dešinėje: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Prijungta (palaikomas garso įrašų bendrinimas). Akumuliatoriaus lygis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Bevardė giminė"</string> <string name="feminine" msgid="1529155595310784757">"Moteriškoji giminė"</string> <string name="masculine" msgid="4653978041013996303">"Vyriškoji giminė"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Sistemos naujiniai"</string> </resources> diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml index 55fa4d3bae6a..c07fae3894c3 100644 --- a/packages/SettingsLib/res/values-lv/strings.xml +++ b/packages/SettingsLib/res/values-lv/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Savienojums izveidots <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (nav tālrunis), akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Savienojums izveidots (<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>) (nav multivide), akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Savienojums izveidots (nav tālrunis vai multivide) (<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>), akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktīvs. Akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktīvs. Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktīvs. Kreisā puse: akumulatora uzlādes līmenis ir <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktīvs. Labā puse: akumulatora uzlādes līmenis ir <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Akumulatora uzlādes līmenis labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktīvs"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Saglabāta"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ierīce aktīva (tikai kreisā auss)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Ierīce aktīva (tikai labā auss)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ierīces aktīvas (kreisā un labā auss)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktīvs (tikai multividei). Akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktīvs (tikai multividei). Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Izveidots savienojums (atbalsta audio kopīgošanu). Akumulatora uzlādes līmenis: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Izveidots savienojums (atbalsta audio kopīgošanu). Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Izveidots savienojums (atbalsta audio kopīgošanu). Akumulatora uzlādes līmenis kreisajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Izveidots savienojums (atbalsta audio kopīgošanu). Akumulatora uzlādes līmenis labajā austiņā: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Izveidots savienojums (atbalsta audio kopīgošanu)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktīvs (tikai multividei)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Atbalsta audio kopīgošanu"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktīvs (tikai multivide), tikai kreisās puses aparāts"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Nekatrā dzimte"</string> <string name="feminine" msgid="1529155595310784757">"Sieviešu dzimte"</string> <string name="masculine" msgid="4653978041013996303">"Vīriešu dzimte"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Sistēmas atjauninājumi"</string> </resources> diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml index 6d3d54c5d5f5..be8d039ec07d 100644 --- a/packages/SettingsLib/res/values-mk/strings.xml +++ b/packages/SettingsLib/res/values-mk/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Лево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Десно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активен"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Зачувано"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Активно (само лево)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Активно (само десно)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Активно (лево и десно)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Активно (само аудиовизуелни содржини). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Активно (само аудиовизуелни содржини). Л: <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_battery_level_lea_support" msgid="5968584103507988820">"Поврзано (поддржува споделување аудио). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Поврзано (поддржува споделување аудио). Л: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Поврзано (поддржува споделување аудио). Лево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Поврзано (поддржува споделување аудио). Десно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерија."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Поврзано (поддржува споделување аудио)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Активно (само аудиовизуелни содржини)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Поддржува споделување аудио"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Активно (само аудиовизуелни содржини), само лево"</string> @@ -176,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> @@ -250,7 +233,7 @@ <item msgid="6946761421234586000">"400 %"</item> </string-array> <string name="choose_profile" msgid="343803890897657450">"Изберете профил"</string> - <string name="category_personal" msgid="6236798763159385225">"Лични"</string> + <string name="category_personal" msgid="6236798763159385225">"Личен"</string> <string name="category_work" msgid="4014193632325996115">"Работа"</string> <string name="category_private" msgid="4244892185452788977">"Приватен"</string> <string name="category_clone" msgid="1554511758987195974">"Клон"</string> @@ -388,7 +371,7 @@ <string name="media_category" msgid="8122076702526144053">"Аудиовизуелни содржини"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"Следење"</string> <string name="strict_mode" msgid="889864762140862437">"Овозможен е строг режим"</string> - <string name="strict_mode_summary" msgid="1838248687233554654">"Осветли екран при долги операции на главна нишка"</string> + <string name="strict_mode_summary" msgid="1838248687233554654">"Трепкај со екранот при долги операции на главна нишка"</string> <string name="pointer_location" msgid="7516929526199520173">"Локација на покажувач"</string> <string name="pointer_location_summary" msgid="957120116989798464">"Прекривката на екран ги покажува тековните податоци на допир"</string> <string name="show_touches" msgid="8437666942161289025">"Прикажувај допири"</string> @@ -653,7 +636,7 @@ <string name="add_user_failed" msgid="4809887794313944872">"Не успеа да создаде нов корисник"</string> <string name="add_guest_failed" msgid="8074548434469843443">"Не успеа создавањето нов гостин"</string> <string name="user_nickname" msgid="262624187455825083">"Прекар"</string> - <string name="edit_user_info_message" msgid="6677556031419002895">"Името и сликата што ќе ги изберете ќе бидат видливи за секој што го користи уредов."</string> + <string name="edit_user_info_message" msgid="6677556031419002895">"Името и сликата што ќе ги изберете ќе бидат видливи за секого што го користи уредов."</string> <string name="user_add_user" msgid="7876449291500212468">"Додајте корисник"</string> <string name="guest_new_guest" msgid="3482026122932643557">"Додајте гостин"</string> <string name="guest_exit_guest" msgid="5908239569510734136">"Отстрани гостин"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Среден род"</string> <string name="feminine" msgid="1529155595310784757">"Женски род"</string> <string name="masculine" msgid="4653978041013996303">"Машки род"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Системски ажурирања"</string> </resources> diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml index 3da1218407ed..5f21c34e8316 100644 --- a/packages/SettingsLib/res/values-ml/strings.xml +++ b/packages/SettingsLib/res/values-ml/strings.xml @@ -96,10 +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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"വലത് വശത്ത്: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ബാറ്ററി"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"സജീവം"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"സംരക്ഷിച്ചു"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"സജീവമാണ് (ഇടതുഭാഗത്ത് മാത്രം)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"സജീവമാണ് (വലതുഭാഗത്ത് മാത്രം)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"സജീവമാണ് (ഇടതുഭാഗത്തും വലതുഭാഗത്തും)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"സജീവം (മീഡിയ മാത്രം). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ബാറ്ററി."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"സജീവം (മീഡിയ മാത്രം). ഇടതുവശത്ത്: <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_battery_level_lea_support" msgid="5968584103507988820">"കണക്റ്റ് ചെയ്തു (ഓഡിയോ പങ്കിടൽ പിന്തുണയ്ക്കുന്നു). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ബാറ്ററി."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"നപുംസകലിംഗം"</string> <string name="feminine" msgid="1529155595310784757">"സ്ത്രീലിംഗം"</string> <string name="masculine" msgid="4653978041013996303">"പുല്ലിംഗം"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"സിസ്റ്റം അപ്ഡേറ്റുകൾ"</string> </resources> diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml index c39717c3e0c2..66efe8b3cf9a 100644 --- a/packages/SettingsLib/res/values-mn/strings.xml +++ b/packages/SettingsLib/res/values-mn/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Зүүн: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Баруун: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Идэвхтэй"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Хадгалсан"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Идэвхтэй (зөвхөн зүүн тал)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Идэвхтэй (зөвхөн баруун тал)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Идэвхтэй (зүүн, баруун тал)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Идэвхтэй (зөвхөн медиа). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Идэвхтэй (зөвхөн медиа). З: <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_battery_level_lea_support" msgid="5968584103507988820">"Холбогдсон (аудио хуваалцахыг дэмждэг). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Холбогдсон (аудио хуваалцахыг дэмждэг). З: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Холбогдсон (аудио хуваалцахыг дэмждэг). Зүүн: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Холбогдсон (аудио хуваалцахыг дэмждэг). Баруун: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батарей."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Холбогдсон (аудио хуваалцахыг дэмждэг)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Идэвхтэй (зөвхөн медиа)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Аудио хуваалцахыг дэмждэг"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Идэвхтэй (зөвхөн медиа), зөвхөн зүүн"</string> @@ -406,8 +389,8 @@ <string name="disable_overlays_summary" msgid="1954852414363338166">"Дэлгэц нийлүүлэхэд GPU-г байнга ашиглах"</string> <string name="simulate_color_space" msgid="1206503300335835151">"Өнгөний орчныг дууриах"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"OpenGL тэмдэглэлийг идэвхжүүлэх"</string> - <string name="usb_audio_disable_routing" msgid="3367656923544254975">"USB аудио чиглүүлэхийг идэвхгүйжүүлэх"</string> - <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"USB аудио нэмэлт хэрэгсэл рүү автоматаар чиглүүлэхийг идэвхгүйжүүлэх"</string> + <string name="usb_audio_disable_routing" msgid="3367656923544254975">"USB аудио чиглүүлэхийг идэвхгүй болгох"</string> + <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"USB аудио нэмэлт хэрэгсэл рүү автоматаар чиглүүлэхийг идэвхгүй болгох"</string> <string name="debug_layout" msgid="1659216803043339741">"Байршлын хүрээг харуулах"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"Клипийн зах, хязгаар зэргийг харуулах"</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"RTL байрлалын чиглэлийг хүчээр тогтоох"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Саармаг үг"</string> <string name="feminine" msgid="1529155595310784757">"Эм үг"</string> <string name="masculine" msgid="4653978041013996303">"Эр үг"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Системийн шинэчлэлт"</string> </resources> diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml index 549c7849c38c..fecb91f1257a 100644 --- a/packages/SettingsLib/res/values-mr/strings.xml +++ b/packages/SettingsLib/res/values-mr/strings.xml @@ -96,10 +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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"उजवीकडे: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> बॅटरी"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"अॅक्टिव्ह"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"सेव्ह केली आहेत"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"अॅक्टिव्ह आहे (फक्त डावे)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"अॅक्टिव्ह आहे (फक्त उजवे)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"अॅक्टिव्ह आहे (डावे आणि उजवे)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"अॅक्टिव्ह आहे (फक्त मीडिया). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> बॅटरी."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"ॲक्टिव्ह आहे (फक्त मीडिया). डावीकडे: <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_battery_level_lea_support" msgid="5968584103507988820">"कनेक्ट केले आहे (ऑडिओ शेअरिंगला सपोर्ट करते). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> बॅटरी."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"नपुसकलिंग"</string> <string name="feminine" msgid="1529155595310784757">"स्त्रीलिंग"</string> <string name="masculine" msgid="4653978041013996303">"पुल्लिंग"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"सिस्टीम अपडेट"</string> </resources> diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml index e62164327dc8..e8e9e7eb3b07 100644 --- a/packages/SettingsLib/res/values-ms/strings.xml +++ b/packages/SettingsLib/res/values-ms/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Disambungkan (tiada telefon atau media), bateri <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">"Aktif. Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktif. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktif. L: Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktif. R: Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Kanan: Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktif"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Disimpan"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktif (kiri sahaja)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktif (kanan sahaja)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktif (kiri dan kanan)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktif (media sahaja). Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktif (media sahaja), L: Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Disambungkan (menyokong perkongsian audio). Bateri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> @@ -240,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Pilih profil"</string> <string name="category_personal" msgid="6236798763159385225">"Peribadi"</string> <string name="category_work" msgid="4014193632325996115">"Tempat Kerja"</string> - <string name="category_private" msgid="4244892185452788977">"Peribadi"</string> + <string name="category_private" msgid="4244892185452788977">"Persendirian"</string> <string name="category_clone" msgid="1554511758987195974">"Klon"</string> <string name="development_settings_title" msgid="140296922921597393">"Pilihan pembangun"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Dayakan pilihan pembangun"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neuter"</string> <string name="feminine" msgid="1529155595310784757">"Feminin"</string> <string name="masculine" msgid="4653978041013996303">"Maskulin"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Kemaskinian Sistem"</string> </resources> diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml index f4ee284971b0..b3317e426598 100644 --- a/packages/SettingsLib/res/values-my/strings.xml +++ b/packages/SettingsLib/res/values-my/strings.xml @@ -94,39 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> + <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">"အသုံးပြုနေသည်။ ဘက်ထရီ 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>။"</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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"ဘက်ထရီ 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>။"</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ဘယ်- ဘက်ထရီ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ညာ- ဘက်ထရီ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ဖွင့်ထားသည်"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"သိမ်းထားသည်များ"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"သုံးနေသည် (ဘယ်ဘက်သီးသန့်)"</string> <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"သုံးနေသည် (ညာဘက်သီးသန့်)"</string> <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"သုံးနေသည် (ဘယ်နှင့်ညာ)"</string> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"အသုံးပြုနေသည် (မီဒီယာသီးသန့်)။ ဘက်ထရီ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>။"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"အသုံးပြုနေသည် (မီဒီယာသီးသန့်)။ ဘက်ထရီ 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>။"</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"ချိတ်ဆက်ထားသည် (အော်ဒီယို မျှဝေခြင်း ပံ့ပိုးသည်)။ ဘက်ထရီ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>။"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"ချိတ်ဆက်ထားသည် (အော်ဒီယို မျှဝေခြင်း ပံ့ပိုးသည်)။ ဘက်ထရီ 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>။"</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"ချိတ်ဆက်ထားသည် (အော်ဒီယို မျှဝေခြင်း ပံ့ပိုးသည်)။ ဘယ်- ဘက်ထရီ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>။"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"ချိတ်ဆက်ထားသည် (အော်ဒီယို မျှဝေခြင်း ပံ့ပိုးသည်)။ ညာ- ဘက်ထရီ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>။"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"ချိတ်ဆက်ထားသည် (အော်ဒီယို မျှဝေခြင်း ပံ့ပိုးသည်)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"သုံးနေသည် (မီဒီယာသီးသန့်)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"အော်ဒီယို မျှဝေခြင်း ပံ့ပိုးသည်"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"သုံးနေသည် (မီဒီယာသီးသန့်)၊ ဘယ်သီးသန့်"</string> @@ -171,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> နှင့် တွဲချိတ်၍မရပါ။"</string> + <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"ပင်နံပါတ် (သို့) လျှို့ဝှက်ကီး မမှန်ကန်သောကြောင့် <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> @@ -383,7 +371,7 @@ <string name="media_category" msgid="8122076702526144053">"မီဒီယာ"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"စောင့်ကြည့်စစ်ဆေးခြင်း"</string> <string name="strict_mode" msgid="889864762140862437">"တင်းကြပ်သောစနစ် ဖွင့်ရန်"</string> - <string name="strict_mode_summary" msgid="1838248687233554654">"အက်ပ်လုပ်ဆောင်မှု ရှည်ကြာလျှင် စကရင်ပြန်စပါ"</string> + <string name="strict_mode_summary" msgid="1838248687233554654">"အက်ပ်လုပ်ဆောင်မှု ရှည်ကြာလျှင် စကရင်ပြန်စသည်"</string> <string name="pointer_location" msgid="7516929526199520173">"မြား၏တည်နေရာ"</string> <string name="pointer_location_summary" msgid="957120116989798464">"လက်ရှိထိတွေ့မှုဒေတာကို ဖန်သားပေါ်တွင်ထပ်၍ ပြသသည်"</string> <string name="show_touches" msgid="8437666942161289025">"တို့ခြင်းများကို ပြပါ"</string> @@ -401,8 +389,8 @@ <string name="disable_overlays_summary" msgid="1954852414363338166">"GPU ကိုမျက်နှာပြင်ခင်းကျင်းရာတွင် အမြဲသုံးပါ။"</string> <string name="simulate_color_space" msgid="1206503300335835151">"အရောင်စနစ် ပြောင်းရန်"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"OpenGL ခြေရာခံခြင်းဖွင့်ပါ။"</string> - <string name="usb_audio_disable_routing" msgid="3367656923544254975">"USB အသံလမ်းကြောင်း ပိတ်ခြင်း"</string> - <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"USB အသံစက်ပစ္စည်းများသို့ အလိုအလျောက် ချိတ်ဆက်ခြင်းကို ပိတ်ရန်"</string> + <string name="usb_audio_disable_routing" msgid="3367656923544254975">"USB အသံလမ်းကြောင်း ပိတ်ရန်"</string> + <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"USB အသံစက်ပစ္စည်းများသို့ အလိုအလျောက် ချိတ်ဆက်ခြင်းကို ပိတ်သည်"</string> <string name="debug_layout" msgid="1659216803043339741">"ပြကွက်၏ဘောင်များ ပြရန်"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"ဖြတ်ပိုင်းအနားသတ်များ၊ အနားများ စသဖြင့် ပြပါ။"</string> <string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"RTL အပြင်အဆင်အတိုင်း ဖြစ်စေခြင်း"</string> @@ -733,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"နပုလ္လိင်"</string> <string name="feminine" msgid="1529155595310784757">"ဣတ္ထိလိင်"</string> <string name="masculine" msgid="4653978041013996303">"ပုလ္လိင်"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"စနစ်အပ်ဒိတ်များ"</string> </resources> diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml index d7814eb37210..f1ea63be900d 100644 --- a/packages/SettingsLib/res/values-nb/strings.xml +++ b/packages/SettingsLib/res/values-nb/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Koblet til (ingen telefon), batteri <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">"Koblet til (ingen medier), batteri <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">"Koblet til (ingen telefon eller medier), batteri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktiv. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktiv. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktiv. V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktiv. H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batteri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Venstre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Høyre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Lagret"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiv (bare venstre)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktiv (bare høyre)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktiv (venstre og høyre)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktiv (bare medieinnhold) <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiv (bare medieinnhold). V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> batteri, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Tilkoblet (støtter lyddeling). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Tilkoblet (støtter lyddeling). V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> batteri, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Tilkoblet (støtter lyddeling). Venstre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Tilkoblet (støtter lyddeling). Høyre: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Tilkoblet (støtter lyddeling)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktiv (bare medieinnhold)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Støtter lyddeling"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktiv (bare medieinnhold), bare venstre"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Intetkjønn"</string> <string name="feminine" msgid="1529155595310784757">"Hunkjønn"</string> <string name="masculine" msgid="4653978041013996303">"Hankjønn"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Systemoppdateringer"</string> </resources> diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml index abe48e9ac210..8bafef3caf4f 100644 --- a/packages/SettingsLib/res/values-ne/strings.xml +++ b/packages/SettingsLib/res/values-ne/strings.xml @@ -96,10 +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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"दायाँ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ब्याट्री"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"सक्रिय"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"सेभ गरिएको"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"सक्रिय छ (बायाँ मात्र)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"सक्रिय छ (दायाँ मात्र)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"सक्रिय छ (दायाँ र बायाँ)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"सक्रिय छ (मिडिया मात्र)। <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ब्याट्री।"</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"सक्रिय छ (मिडिया मात्र)। बायाँ: <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_battery_level_lea_support" msgid="5968584103507988820">"कनेक्ट गरिएको छ (अडियो सेयर गर्न मिल्छ)। <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ब्याट्री।"</string> @@ -162,9 +157,9 @@ <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_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> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"नपुंसक लिङ्ग"</string> <string name="feminine" msgid="1529155595310784757">"स्त्रीलिङ्ग"</string> <string name="masculine" msgid="4653978041013996303">"पुलिङ्ग"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"सिस्टम अपडेटहरू"</string> </resources> diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml index 3f2e00382e70..00c3acb6ebd4 100644 --- a/packages/SettingsLib/res/values-nl/strings.xml +++ b/packages/SettingsLib/res/values-nl/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Verbonden: <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (geen telefoon), batterij: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Verbonden: <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (geen media), batterij: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Verbonden: <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (geen telefoon of media), batterij: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Actief. Batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Actief. L: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Actief. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batterij."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Actief. R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batterij."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batterij <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"L: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Links: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Rechts: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Actief"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Opgeslagen"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Actief (alleen links)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Actief (alleen rechts)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Actief (links en rechts)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Actief (alleen media). Batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Actief (alleen media), L: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Verbonden (ondersteunt audio delen), batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Verbonden (ondersteunt audio delen), L: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, R: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Verbonden (ondersteunt audio delen). Links: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Verbonden (ondersteunt audio delen). Rechts: batterijniveau <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Verbonden (ondersteunt audio delen)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Actief (alleen media)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Ondersteunt audio delen"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Actief (alleen media), alleen links"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Onzijdig"</string> <string name="feminine" msgid="1529155595310784757">"Vrouwelijk"</string> <string name="masculine" msgid="4653978041013996303">"Mannelijk"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Systeemupdates"</string> </resources> diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml index b233b5448eae..ab150c2b4140 100644 --- a/packages/SettingsLib/res/values-or/strings.xml +++ b/packages/SettingsLib/res/values-or/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ବାମ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ଡାହାଣ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ସକ୍ରିୟ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ସେଭ କରାଯାଇଛି"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ସକ୍ରିୟ (କେବଳ ବାମ)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"ସକ୍ରିୟ (କେବଳ ଡାହାଣ)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"ସକ୍ରିୟ (ବାମ ଏବଂ ଡାହାଣ)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"ସକ୍ରିୟ (କେବଳ ମିଡିଆ)। <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ।"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"ସକ୍ରିୟ (କେବଳ ମିଡିଆ)। ବାମ: <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_battery_level_lea_support" msgid="5968584103507988820">"କନେକ୍ଟ କରାଯାଇଛି (ଅଡିଓ ସେୟାରିଂକୁ ସମର୍ଥନ କରେ)। <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ।"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"କନେକ୍ଟ କରାଯାଇଛି (ଅଡିଓ ସେୟାରିଂକୁ ସମର୍ଥନ କରେ)। ବାମ: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"କନେକ୍ଟ କରାଯାଇଛି (ଅଡିଓ ସେୟାରିଂକୁ ସମର୍ଥନ କରେ)। ବାମ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ।"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"କନେକ୍ଟ କରାଯାଇଛି (ଅଡିଓ ସେୟାରିଂକୁ ସମର୍ଥନ କରେ)। ଡାହାଣ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ବେଟେରୀ।"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"କନେକ୍ଟ କରାଯାଇଛି (ଅଡିଓ ସେୟାରିଂକୁ ସମର୍ଥନ କରେ)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"ସକ୍ରିୟ (କେବଳ ମିଡିଆ)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"ଅଡିଓ ସେୟାରିଂକୁ ସମର୍ଥନ କରେ"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"ସକ୍ରିୟ (କେବଳ ମିଡିଆ), କେବଳ ବାମ"</string> @@ -249,7 +232,7 @@ <item msgid="4446831566506165093">"350%"</item> <item msgid="6946761421234586000">"400%"</item> </string-array> - <string name="choose_profile" msgid="343803890897657450">"ପ୍ରୋଫାଇଲ୍ ବାଛନ୍ତୁ"</string> + <string name="choose_profile" msgid="343803890897657450">"ପ୍ରୋଫାଇଲ ବାଛନ୍ତୁ"</string> <string name="category_personal" msgid="6236798763159385225">"ବ୍ୟକ୍ତିଗତ"</string> <string name="category_work" msgid="4014193632325996115">"ୱାର୍କ"</string> <string name="category_private" msgid="4244892185452788977">"ପ୍ରାଇଭେଟ"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ନ୍ୟୁଟର"</string> <string name="feminine" msgid="1529155595310784757">"ସ୍ତ୍ରୀଲିଙ୍ଗ"</string> <string name="masculine" msgid="4653978041013996303">"ପୁଲିଙ୍ଗ"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"ସିଷ୍ଟମ ଅପଡେଟଗୁଡ଼ିକ"</string> </resources> diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml index 73fe924e4b95..0e77a04a0f74 100644 --- a/packages/SettingsLib/res/values-pa/strings.xml +++ b/packages/SettingsLib/res/values-pa/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ਖੱਬੇ ਪਾਸੇ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ਬੈਟਰੀ"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ਸੱਜੇ ਪਾਸੇ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ਬੈਟਰੀ"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ਕਿਰਿਆਸ਼ੀਲ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"ਰੱਖਿਅਤ ਕੀਤਾ ਗਿਆ"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ਕਿਰਿਆਸ਼ੀਲ (ਸਿਰਫ਼ ਖੱਬਾ)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"ਕਿਰਿਆਸ਼ੀਲ (ਸਿਰਫ਼ ਸੱਜਾ)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"ਕਿਰਿਆਸ਼ੀਲ (ਖੱਬਾ ਅਤੇ ਸੱਜਾ)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"ਕਿਰਿਆਸ਼ੀਲ (ਸਿਰਫ਼ ਮੀਡੀਆ)। <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ਬੈਟਰੀ।"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"ਕਿਰਿਆਸ਼ੀਲ (ਸਿਰਫ਼ ਮੀਡੀਆ)। ਖੱਬੇ ਪਾਸੇ: <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_battery_level_lea_support" msgid="5968584103507988820">"ਕਨੈਕਟ ਕੀਤਾ (ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ)। <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ਬੈਟਰੀ।"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"ਕਨੈਕਟ ਕੀਤਾ (ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ)। ਖੱਬੇ ਪਾਸੇ: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"ਕਨੈਕਟ ਕੀਤਾ (ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ)। ਖੱਬੇ ਪਾਸੇ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ਬੈਟਰੀ।"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"ਕਨੈਕਟ ਕੀਤਾ (ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ)। ਸੱਜੇ ਪਾਸੇ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ਬੈਟਰੀ।"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"ਕਨੈਕਟ ਕੀਤਾ (ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"ਕਿਰਿਆਸ਼ੀਲ (ਸਿਰਫ਼ ਮੀਡੀਆ)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"ਆਡੀਓ ਸਾਂਝਾਕਰਨ ਦਾ ਸਮਰਥਨ ਕਰਦਾ ਹੈ"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"ਕਿਰਿਆਸ਼ੀਲ (ਸਿਰਫ਼ ਮੀਡੀਆ), ਸਿਰਫ਼ ਖੱਬਾ"</string> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ"</string> <string name="category_personal" msgid="6236798763159385225">"ਨਿੱਜੀ"</string> <string name="category_work" msgid="4014193632325996115">"ਕੰਮ ਸੰਬੰਧੀ"</string> - <string name="category_private" msgid="4244892185452788977">"ਨਿੱਜੀ"</string> + <string name="category_private" msgid="4244892185452788977">"ਪ੍ਰਾਈਵੇਟ"</string> <string name="category_clone" msgid="1554511758987195974">"ਕਲੋਨ ਕਰੋ"</string> <string name="development_settings_title" msgid="140296922921597393">"ਵਿਕਾਸਕਾਰ ਚੋਣਾਂ"</string> <string name="development_settings_enable" msgid="4285094651288242183">"ਵਿਕਾਸਕਾਰ ਵਿਕਲਪਾਂ ਨੂੰ ਚਾਲੂ ਕਰੋ"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ਨਿਰਪੱਖ"</string> <string name="feminine" msgid="1529155595310784757">"ਇਸਤਰੀ-ਲਿੰਗ"</string> <string name="masculine" msgid="4653978041013996303">"ਪੁਲਿੰਗ"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"ਸਿਸਟਮ ਅੱਪਡੇਟ"</string> </resources> diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml index 69071027ffc4..0906d4bb6367 100644 --- a/packages/SettingsLib/res/values-pl/strings.xml +++ b/packages/SettingsLib/res/values-pl/strings.xml @@ -95,28 +95,23 @@ <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Połączono (bez multimediów), bateria: <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">"Połączono (bez telefonu i multimediów), bateria <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">"Aktywne. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> naładowania baterii."</string> - <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktywne. Lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> naładowania baterii, prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> naładowania baterii."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktywne. Lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> naładowania baterii."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktywne. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterii."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktywne. P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterii."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> naładowania baterii"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Bateria <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> naładowania baterii, prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> naładowania baterii."</string> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> naładowania baterii."</string> <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> naładowania baterii"</string> <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> naładowania baterii"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Urządzenie aktywne"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Zapisano"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktywne (tylko lewa strona)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktywne (tylko prawa strona)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktywne (lewa i prawa strona)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktywne (tylko multimedia). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> naładowania baterii."</string> - <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktywne (tylko multimedia), lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> baterii, prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> naładowania baterii."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktywne (tylko multimedia), lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> naładowania baterii."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Połączone (obsługa udostępniania dźwięku), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> naładowania baterii."</string> - <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Połączone (obsługa udostępniania dźwięku), lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> naładowania baterii, prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> naładowania baterii."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Połączone (obsługa udostępniania dźwięku), lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> naładowania baterii."</string> <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Połączone (obsługa udostępniania dźwięku). Lewa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> naładowania baterii."</string> <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Połączone (obsługa udostępniania dźwięku). Prawa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> naładowania baterii."</string> <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Połączone (obsługa udostępniania dźwięku)"</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Nijaki"</string> <string name="feminine" msgid="1529155595310784757">"Żeński"</string> <string name="masculine" msgid="4653978041013996303">"Męski"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Aktualizacje systemu"</string> </resources> diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml index 5bc01f11bbb5..d054d9b08d68 100644 --- a/packages/SettingsLib/res/values-pt-rBR/strings.xml +++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Conectado (sem telefone), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> de bateria"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Conectado (sem mídia), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> de bateria"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Conectado (sem telefone ou mídia), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> de bateria"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Ativo. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Ativo. 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_active_battery_level_untethered_left" msgid="2895644748625343977">"Ativo. Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Ativo. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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" msgid="5725764679536058365">"Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ativo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Salvo"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ativo (apenas o esquerdo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Ativo (apenas o direito)"</string> + <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 (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> @@ -592,7 +575,7 @@ <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Alto-falante da base"</string> <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string> <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string> - <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este telefone"</string> + <string name="media_transfer_this_phone" msgid="7194341457812151531">"Neste telefone"</string> <string name="media_output_status_unknown_error" msgid="5098565887497902222">"Não é possível reproduzir neste dispositivo"</string> <string name="media_output_status_require_premium" msgid="8411255800047014822">"Faça upgrade da conta para trocar"</string> <string name="media_output_status_not_support_downloads" msgid="4523828729240373315">"Não é possível abrir os downloads aqui"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutro"</string> <string name="feminine" msgid="1529155595310784757">"Feminino"</string> <string name="masculine" msgid="4653978041013996303">"Masculino"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Atualizações do sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml index 671c9f759dbc..0bcca1566a8e 100644 --- a/packages/SettingsLib/res/values-pt-rPT/strings.xml +++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml @@ -94,39 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Ligado (sem telemóvel), bateria a <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">"Ligado (sem multimédia), bateria a <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">"Ligado (sem telemóvel nem multimédia), bateria a <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Ativo. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Ativo. E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Ativo. E: bateria a <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Ativo. D: bateria a <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> de bateria"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Bateria. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ativo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Guardado"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ativo (apenas esquerdo)"</string> <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Ativo (apenas direito)"</string> <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Ativo (esquerdo e direito)"</string> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Ativo (apenas para multimé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 para multimédia). E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> de bateria."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Ligado (suporta partilha 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">"Ligado (suporta partilha de áudio). E: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> de bateria. D: <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">"Ligado (suporta a partilha de áudio). 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">"Ligado (suporta a partilha de áudio). 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">"Ligado (suporta a partilha de áudio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Ativo (apenas para multimédia)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Suporta partilha de áudio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Ativo (apenas para multimédia), apenas esquerdo"</string> @@ -244,7 +232,7 @@ <item msgid="4446831566506165093">"350%"</item> <item msgid="6946761421234586000">"400%"</item> </string-array> - <string name="choose_profile" msgid="343803890897657450">"Escolher perfil"</string> + <string name="choose_profile" msgid="343803890897657450">"Escolha o perfil"</string> <string name="category_personal" msgid="6236798763159385225">"Pessoal"</string> <string name="category_work" msgid="4014193632325996115">"Trabalho"</string> <string name="category_private" msgid="4244892185452788977">"Privado"</string> @@ -733,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutro"</string> <string name="feminine" msgid="1529155595310784757">"Feminino"</string> <string name="masculine" msgid="4653978041013996303">"Masculino"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Atualizações do sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml index 5bc01f11bbb5..d054d9b08d68 100644 --- a/packages/SettingsLib/res/values-pt/strings.xml +++ b/packages/SettingsLib/res/values-pt/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Conectado (sem telefone), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> de bateria"</string> <string name="bluetooth_connected_no_a2dp_battery_level" msgid="6499078454894324287">"Conectado (sem mídia), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> de bateria"</string> <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Conectado (sem telefone ou mídia), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> de bateria"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Ativo. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Ativo. 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_active_battery_level_untethered_left" msgid="2895644748625343977">"Ativo. Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Ativo. Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Bateria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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" msgid="5725764679536058365">"Lado esquerdo: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Lado direito: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> de bateria"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Ativo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Salvo"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Ativo (apenas o esquerdo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Ativo (apenas o direito)"</string> + <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 (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> @@ -592,7 +575,7 @@ <string name="media_transfer_dock_speaker_device_name" msgid="2856219597113881950">"Alto-falante da base"</string> <string name="media_transfer_external_device_name" msgid="2588672258721846418">"Dispositivo externo"</string> <string name="media_transfer_default_device_name" msgid="4315604017399871828">"Dispositivo conectado"</string> - <string name="media_transfer_this_phone" msgid="7194341457812151531">"Este telefone"</string> + <string name="media_transfer_this_phone" msgid="7194341457812151531">"Neste telefone"</string> <string name="media_output_status_unknown_error" msgid="5098565887497902222">"Não é possível reproduzir neste dispositivo"</string> <string name="media_output_status_require_premium" msgid="8411255800047014822">"Faça upgrade da conta para trocar"</string> <string name="media_output_status_not_support_downloads" msgid="4523828729240373315">"Não é possível abrir os downloads aqui"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutro"</string> <string name="feminine" msgid="1529155595310784757">"Feminino"</string> <string name="masculine" msgid="4653978041013996303">"Masculino"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Atualizações do sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml index 4d6638aa6441..ea97c371329e 100644 --- a/packages/SettingsLib/res/values-ro/strings.xml +++ b/packages/SettingsLib/res/values-ro/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Conectat (fără telefon), baterie <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">"Conectat (fără conținut media), baterie <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">"Conectat (fără telefon sau conținut media), baterie <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Activ. Nivelul bateriei <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Activ. Nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, dreapta: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Activ. Nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Activ. Nivelul bateriei din dreapta: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Nivelul bateriei: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Baterie: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, dreapta: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Nivelul bateriei din dreapta:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Activ"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Salvat"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Activ (numai stânga)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Activ (numai dreapta)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Activ (stânga și dreapta)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Activ (numai pentru conținut media). Nivelul bateriei <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Activ (numai pentru conținut media): nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, nivelul bateriei din dreapta: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>"</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Conectat (acceptă permiterea accesului la audio). Nivelul bateriei: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Conectat (acceptă permiterea accesului la audio), nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, nivelul bateriei din dreapta: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Conectat (acceptă permiterea accesului la audio). Nivelul bateriei din stânga: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Conectat (acceptă permiterea accesului la audio). Nivelul bateriei din dreapta: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Conectat (acceptă permiterea accesului la audio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Activ (numai pentru conținut media)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Acceptă permiterea accesului la audio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Activ (numai pentru conținut media), numai stânga"</string> @@ -406,7 +389,7 @@ <string name="disable_overlays_summary" msgid="1954852414363338166">"Folosește mereu GPU pentru compunerea ecranului"</string> <string name="simulate_color_space" msgid="1206503300335835151">"Simulează spațiu culoare"</string> <string name="enable_opengl_traces_title" msgid="4638773318659125196">"Monitorizări OpenGL"</string> - <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Dezactivați rutarea audio USB"</string> + <string name="usb_audio_disable_routing" msgid="3367656923544254975">"Dezactivează rutarea audio USB"</string> <string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Dezact. rutarea automată către perif. audio USB"</string> <string name="debug_layout" msgid="1659216803043339741">"Afișează limite aspect"</string> <string name="debug_layout_summary" msgid="8825829038287321978">"Afișează limitele clipului, marginile etc."</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutru"</string> <string name="feminine" msgid="1529155595310784757">"Feminin"</string> <string name="masculine" msgid="4653978041013996303">"Masculin"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Actualizări de sistem"</string> </resources> diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml index e155948849f3..aaf648ea98a5 100644 --- a/packages/SettingsLib/res/values-ru/strings.xml +++ b/packages/SettingsLib/res/values-ru/strings.xml @@ -94,47 +94,30 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (Л)"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (П)"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активно"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Сохранено"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Используется (только левый)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Используется (только правый)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Используется (левый и правый)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Используется (только для медиа), заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Используется (только для медиа), заряд: <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_battery_level_lea_support" msgid="5968584103507988820">"Подключено (поддерживается отправка аудио), заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Подключено (поддерживается отправка аудио), заряд: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Подключено (поддерживается отправка аудио), заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (Л)."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Подключено (поддерживается отправка аудио), заряд: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (П)."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Подключено (поддерживается отправка аудио)."</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Используется (только для медиа)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Поддерживается отправка аудио"</string> - <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Используется (только для медиа), левый наушник"</string> + <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Используется (только для медиа), только левый"</string> <string name="bluetooth_hearing_aid_media_only_right_active" msgid="3854140683042617230">"Используется (только для медиа), правый наушник"</string> <string name="bluetooth_hearing_aid_media_only_left_and_right_active" msgid="1299913413062528417">"Используется (только для медиа), левый и правый наушники"</string> <string name="bluetooth_profile_a2dp" msgid="4632426382762851724">"Профиль A2DP"</string> @@ -176,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> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Выбор профиля"</string> <string name="category_personal" msgid="6236798763159385225">"Личный профиль"</string> <string name="category_work" msgid="4014193632325996115">"Рабочий профиль"</string> - <string name="category_private" msgid="4244892185452788977">"Личный профиль"</string> + <string name="category_private" msgid="4244892185452788977">"Частный профиль"</string> <string name="category_clone" msgid="1554511758987195974">"Клон"</string> <string name="development_settings_title" msgid="140296922921597393">"Для разработчиков"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Включить параметры для разработчиков"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Средний"</string> <string name="feminine" msgid="1529155595310784757">"Женский"</string> <string name="masculine" msgid="4653978041013996303">"Мужской"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Обновления системы"</string> </resources> diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml index 0c494e4d7c32..ab423b5d7f93 100644 --- a/packages/SettingsLib/res/values-si/strings.xml +++ b/packages/SettingsLib/res/values-si/strings.xml @@ -96,10 +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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"දකුණ: බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ක්රියාකාරී"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"සුරැකිණි"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"සක්රිය (වම පමණි)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"සක්රිය (දකුණ පමණි)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"සක්රිය (වම සහ දකුණ)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"ක්රියාත්මකයි (මාධ්ය පමණයි). බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"ක්රියාත්මකයි (මාධ්ය පමණයි), බැටරිය ව: <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_battery_level_lea_support" msgid="5968584103507988820">"සම්බන්ධයි (ශ්රව්ය බෙදා ගැනීමට සහය දක්වයි). බැටරිය <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"නපුංසක"</string> <string name="feminine" msgid="1529155595310784757">"ස්ත්රී"</string> <string name="masculine" msgid="4653978041013996303">"පුරුෂ"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"පද්ධති යාවත්කාලීන"</string> </resources> diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml index fe41bebbeb8c..7922b70228d7 100644 --- a/packages/SettingsLib/res/values-sk/strings.xml +++ b/packages/SettingsLib/res/values-sk/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Pripojené k zariadeniu <xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> (bez telefónu a médií), úroveň batérie <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktívne. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batérie."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktívne. Ľ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batérie."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktívne. Ľ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batérie."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktívne. P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batérie."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Batéria: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batéria: <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>, P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batérie."</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Pravá strana: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batérie"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktívne"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Uložené"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktívne (iba ľavé)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktívne (iba pravé)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktívne (ľavé aj pravé)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktívne (iba médiá). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batérie."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktívne (iba médiá). Ľ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> batérie, P: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batérie."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Pripojené (podporuje zdieľanie zvuku). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batérie."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Stredný rod"</string> <string name="feminine" msgid="1529155595310784757">"Ženský rod"</string> <string name="masculine" msgid="4653978041013996303">"Mužský rod"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Aktualizácie systému"</string> </resources> diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml index 32b6d21dc7f1..d412f571680e 100644 --- a/packages/SettingsLib/res/values-sl/strings.xml +++ b/packages/SettingsLib/res/values-sl/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Povezano (brez telefona ali predstavnosti), raven napolnjenosti baterije je <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">"Aktivno. Baterija: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktivno. Baterija – L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktivno. Baterija – L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktivno. Baterija – D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Baterija na <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Baterija: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Baterija – L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Desno – baterija: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktivna"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Shranjeno"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktivno (samo levo)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktivno (samo desno)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktivno (levo in desno)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktivno (samo predstavnost). Baterija: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktivno (samo predstavnost), baterija – L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, D: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Povezano (podpira deljenje zvoka), baterija: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> @@ -373,7 +368,7 @@ <string name="debug_input_category" msgid="7349460906970849771">"Vnos"</string> <string name="debug_drawing_category" msgid="5066171112313666619">"Risanje"</string> <string name="debug_hw_drawing_category" msgid="5830815169336975162">"Upodabljanje s strojnim pospeševanjem"</string> - <string name="media_category" msgid="8122076702526144053">"Predstavnosti"</string> + <string name="media_category" msgid="8122076702526144053">"Predstavnost"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"Spremljanje"</string> <string name="strict_mode" msgid="889864762140862437">"Strog način je omogočen"</string> <string name="strict_mode_summary" msgid="1838248687233554654">"Osveži zaslon pri dolgih postopkih v glavni niti."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Srednji spol"</string> <string name="feminine" msgid="1529155595310784757">"Ženski spol"</string> <string name="masculine" msgid="4653978041013996303">"Moški spol"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Posodobitve sistema"</string> </resources> diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml index f112060d7764..944d992dfeb2 100644 --- a/packages/SettingsLib/res/values-sq/strings.xml +++ b/packages/SettingsLib/res/values-sq/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"E lidhur (pa telefon), bateria <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">"E lidhur (pa media), bateria <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">"E lidhur (pa telefon ose media), bateria <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktiv. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktiv. Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> bateri, djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> bateri."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktive. Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (niveli i baterisë)."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktive. Djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (niveli i baterisë)."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Bateria <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> bateri"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> bateri, djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> bateri."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Të ruajtura"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktive (vetëm majtas)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktive (vetëm djathtas)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktive (majtas dhe djathtas)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktiv (vetëm për media). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiv (vetëm për media). Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> bateri, djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> bateri."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Lidhur (mbështet ndarjen e audios). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Lidhur (mbështet ndarjen e audios). Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> bateri, djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> bateri."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Lidhur (mbështet ndarjen e audios). Majtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Lidhur (mbështet ndarjen e audios). Djathtas: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> bateri."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Lidhur (mbështet ndarjen e audios)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktiv (vetëm për media)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Mbështet ndarjen e audios"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktiv (vetëm për media), vetëm majtas"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Asnjanëse"</string> <string name="feminine" msgid="1529155595310784757">"Femërore"</string> <string name="masculine" msgid="4653978041013996303">"Mashkullore"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Përditësimet e sistemit"</string> </resources> diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml index 9341616ae29e..1bb801d1b20d 100644 --- a/packages/SettingsLib/res/values-sr/strings.xml +++ b/packages/SettingsLib/res/values-sr/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Лево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерије"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Десно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерије"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активан"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Сачувано"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Активно (само лево)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Активно (само десно)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Активно (лево и десно)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Активно (само за медије). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерије."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Активно (само за медије). Лево: <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_battery_level_lea_support" msgid="5968584103507988820">"Повезано (подржава дељење звука), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерије."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Повезано (подржава дељење звука), лево: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Повезано (подржава дељење звука). Лево: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерије"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Повезано (подржава дељење звука). Десно: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> батерије."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Повезано (подржава дељење звука)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Активан (само за медије)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Подржава дељење звука"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Активан (само за медије), само лево"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Средњи род"</string> <string name="feminine" msgid="1529155595310784757">"Женски род"</string> <string name="masculine" msgid="4653978041013996303">"Мушки род"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Ажурирања система."</string> </resources> diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml index d3370e8ad238..cc3243209c43 100644 --- a/packages/SettingsLib/res/values-sv/strings.xml +++ b/packages/SettingsLib/res/values-sv/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Ansluten (ingen mobil), batterinivå <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">"Ansluten (inga medier), batterinivå <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">"Ansluten (ingen mobil och inga medier), batterinivå <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktiv. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktiv. V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktiv. V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktiv. H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batteri: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Vänster: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Höger: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktiv"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Sparad"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktiv (endast vänster)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktiv (endast höger)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktiv (vänster och höger)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktiv (endast media). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktiv (endast media). V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Ansluten (ljuddelning stöds). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Ansluten (ljuddelning stöds). V: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, H: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Ansluten (ljuddelning stöds). Vänster: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Ansluten (ljuddelning stöds). Höger: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> batteri."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Ansluten (ljuddelning stöds)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktiv (endast media)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Ljuddelning stöds"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktiv (endast media), endast vänster"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Neutrum"</string> <string name="feminine" msgid="1529155595310784757">"Femininformer"</string> <string name="masculine" msgid="4653978041013996303">"Maskulinformer"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Systemuppdateringar"</string> </resources> diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml index f1d22078e05e..0eac15c4a9d6 100644 --- a/packages/SettingsLib/res/values-sw/strings.xml +++ b/packages/SettingsLib/res/values-sw/strings.xml @@ -94,39 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Imeunganishwa (hamna simu), kiasi cha chaji ni <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">"Imeunganishwa (hamna kifaa cha sauti), kiasi cha chaji ni <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">"Imeunganishwa (hamna simu au kifaa cha sauti), kiasi cha chaji ni <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Inatumika. Chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Inatumika. Kushoto: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Kulia: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Inatumika. Kushoto: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Inatumika. Kulia: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Chaji ya betri ni <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Chaji ya betri ni <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Kushoto: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Kulia: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Kushoto: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Kulia: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Kimeunganishwa"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Imeokoa"</string> <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Inatumika (kushoto pekee)"</string> <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Inatumika (kulia pekee)"</string> <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Inatumika (kushoto na kulia)"</string> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Inatumika (maudhui pekee). Chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Inatumika (maudhui pekee), Kushoto: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Kulia: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Imeunganishwa (inaweza kutumia kipengele cha kusikiliza pamoja). Chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Imeunganishwa (inaweza kutumia kipengele cha kusikiliza pamoja). Kushoto: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Kulia: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Imeunganishwa (inaweza kutumia kipengele cha kusikiliza pamoja). Kushoto: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Imeunganishwa (inaweza kutumia kipengele cha kusikiliza pamoja). Kulia: chaji ya betri imefika <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Imeunganishwa (inaweza kutumia kipengele cha kusikiliza pamoja)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Inatumika (maudhui pekee)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Inaweza kutumia kipengele cha kusikiliza pamoja"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Inatumika (maudhui pekee), kushoto pekee"</string> @@ -380,7 +368,7 @@ <string name="debug_input_category" msgid="7349460906970849771">"Ingizo"</string> <string name="debug_drawing_category" msgid="5066171112313666619">"Uchoraji"</string> <string name="debug_hw_drawing_category" msgid="5830815169336975162">"Utekelezaji wa maunzi ulioharakishwa"</string> - <string name="media_category" msgid="8122076702526144053">"Vyombo vya Habari"</string> + <string name="media_category" msgid="8122076702526144053">"Maudhui"</string> <string name="debug_monitoring_category" msgid="1597387133765424994">"Ufuatiliaji"</string> <string name="strict_mode" msgid="889864762140862437">"Hali makinifu imewashwa"</string> <string name="strict_mode_summary" msgid="1838248687233554654">"Fanya skrini imemeteke programu zinapoendeleza shughuli ndefu kwenye skrini kuu"</string> @@ -733,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Isiyobainika"</string> <string name="feminine" msgid="1529155595310784757">"Jinsia ya kike"</string> <string name="masculine" msgid="4653978041013996303">"Jinsia ya kiume"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Masasisho ya Mfumo"</string> </resources> diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml index 8097be06a16c..7c87eb7ff885 100644 --- a/packages/SettingsLib/res/values-ta/strings.xml +++ b/packages/SettingsLib/res/values-ta/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"இடது: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> பேட்டரி"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"வலது: - <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> பேட்டரி"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"செயலில் உள்ளது"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"சேமிக்கப்பட்டது"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"செயலில் உள்ளது (இடதுபுறம் மட்டும்)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"செயலில் உள்ளது (வலதுபுறம் மட்டும்)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"செயலில் உள்ளது (இடது மற்றும் வலதுபுறம்)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"செயலிலுள்ளது (மீடியா மட்டும்). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> பேட்டரி."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"செயலிலுள்ளது (மீடியா மட்டும்). இடது பேட்டரி: <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_battery_level_lea_support" msgid="5968584103507988820">"இணைக்கப்பட்டுள்ளது (ஆடியோ பகிர்வை ஆதரிக்கிறது). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> பேட்டரி."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"இணைக்கப்பட்டுள்ளது (ஆடியோ பகிர்வை ஆதரிக்கிறது). இடது பேட்டரி: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"இணைக்கப்பட்டுள்ளது (ஆடியோ பகிர்வை ஆதரிக்கிறது). இடது: - <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> பேட்டரி."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"இணைக்கப்பட்டுள்ளது (ஆடியோ பகிர்வை ஆதரிக்கிறது). வலது: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> பேட்டரி."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"இணைக்கப்பட்டுள்ளது (ஆடியோ பகிர்வை ஆதரிக்கிறது)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"செயலிலுள்ளது (மீடியா மட்டும்)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"ஆடியோ பகிர்வை ஆதரிக்கிறது"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"செயலிலுள்ளது (மீடியா மட்டும்), இடதுபுறம் மட்டும்"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"அஃறிணை"</string> <string name="feminine" msgid="1529155595310784757">"பெண்"</string> <string name="masculine" msgid="4653978041013996303">"ஆண்"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"சிஸ்டம் புதுப்பிப்புகள்"</string> </resources> diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml index fbedf3306fb5..689ea6f11c95 100644 --- a/packages/SettingsLib/res/values-te/strings.xml +++ b/packages/SettingsLib/res/values-te/strings.xml @@ -96,10 +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> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"కుడి వైపు: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> బ్యాటరీ"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"యాక్టివ్గా ఉంది"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"సేవ్ చేయబడింది"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"యాక్టివ్గా ఉంది (ఎడమ వైపు మాత్రమే)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"యాక్టివ్గా ఉంది (కుడి వైపు మాత్రమే)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"యాక్టివ్గా ఉంది (ఎడమ వైపు, కుడి వైపు)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"యాక్టివ్ (మీడియా మాత్రమే). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> బ్యాటరీ."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"యాక్టివ్ (మీడియా మాత్రమే). ఎడమ వైపు: <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_battery_level_lea_support" msgid="5968584103507988820">"కనెక్ట్ చేయబడింది (ఆడియో షేరింగ్కు సపోర్ట్ చేస్తుంది). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> బ్యాటరీ."</string> @@ -164,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> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"తటస్థం"</string> <string name="feminine" msgid="1529155595310784757">"స్త్రీ"</string> <string name="masculine" msgid="4653978041013996303">"పురుషుడు"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"సిస్టమ్ అప్డేట్లు"</string> </resources> diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml index 8f59f36bba3f..1899914428c7 100644 --- a/packages/SettingsLib/res/values-th/strings.xml +++ b/packages/SettingsLib/res/values-th/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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">"ใช้งานอยู่ 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>"</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"ใช้งานอยู่ L: แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"ใช้งานอยู่ R: แบตเตอรี่ <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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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>"</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"ซ้าย: แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"ขวา: แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"ใช้งานอยู่"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"บันทึกแล้ว"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"ใช้งานอยู่ (เฉพาะข้างซ้าย)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"ใช้งานอยู่ (เฉพาะข้างขวา)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"ใช้งานอยู่ (ข้างซ้ายและขวา)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"ใช้งานอยู่ (สื่อเท่านั้น) แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"ใช้งานอยู่ (สื่อเท่านั้น) 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>"</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"เชื่อมต่อแล้ว (รองรับการแชร์เสียง) แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"เชื่อมต่อแล้ว (รองรับการแชร์เสียง) 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>"</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"เชื่อมต่อแล้ว (รองรับการแชร์เสียง) ซ้าย: แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"เชื่อมต่อแล้ว (รองรับการแชร์เสียง) ขวา: แบตเตอรี่ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"เชื่อมต่อแล้ว (รองรับการแชร์เสียง)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"ใช้งานอยู่ (สื่อเท่านั้น)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"รองรับการแชร์เสียง"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"ใช้งานอยู่ (สื่อเท่านั้น), ซ้ายเท่านั้น"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"ไม่มีเพศ"</string> <string name="feminine" msgid="1529155595310784757">"เพศหญิง"</string> <string name="masculine" msgid="4653978041013996303">"เพศชาย"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"การอัปเดตระบบ"</string> </resources> diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml index 58b6e84ffb41..64f51900fc33 100644 --- a/packages/SettingsLib/res/values-tl/strings.xml +++ b/packages/SettingsLib/res/values-tl/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Nakakonekta (walang telepono), baterya <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">"Nakakonekta (walang media), baterya <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">"Nakakonekta (walang telepono o media), baterya <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Aktibo. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Aktibo. 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> baterya."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Aktibo. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Aktibo. R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Baterya <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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> baterya."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Kaliwa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Kanan: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Aktibo"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Na-save"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Aktibo (kaliwa lang)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Aktibo (kanan lang)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Aktibo (kaliwa at kanan)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Aktibo (media lang). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Aktibo (media lang). 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> baterya."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Nakakonekta (sinusuportahan ang pag-share ng audio), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Nakakonekta (sinusuportahan ang pag-share ng audio). 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> baterya."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Nakakonekta (sinusuportahan ang pag-share ng audio). Kaliwa: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Nakakonekta (sinusuportahan ang pag-share ng audio). Kanan: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> baterya."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Nakakonekta (sinusuportahan ang pag-share ng audio)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Aktibo (media lang)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Sinusuportahan ang pag-share ng audio"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Aktibo (media lang), kaliwa lang"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Walang Kasarian"</string> <string name="feminine" msgid="1529155595310784757">"Pambabae"</string> <string name="masculine" msgid="4653978041013996303">"Panlalaki"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Mga Update sa System"</string> </resources> diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml index 0300ddc1e31d..144c20b7b1e4 100644 --- a/packages/SettingsLib/res/values-tr/strings.xml +++ b/packages/SettingsLib/res/values-tr/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> cihazına bağlandı (telefon yok), pil <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> cihazına bağlandı (medya yok), pil <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> cihazına bağlandı (telefon veya medya yok), pil seviyesi <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Etkin. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Etkin. Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Etkin. Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Etkin. Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Pil düzeyi <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Pil <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Etkin"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Kaydedildi"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Etkin (yalnızca sol taraf)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Etkin (yalnızca sağ taraf)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Etkin (sol ve sağ taraf)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Etkin (yalnızca medya). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Etkin (yalnızca medya), Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Bağlı (ses paylaşımını destekler), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Bağlı (ses paylaşımını destekler), Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g>, Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Bağlı (ses paylaşımını destekler). Sol: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Bağlı (ses paylaşımını destekler). Sağ: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pil seviyesi."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Bağlı (ses paylaşımını destekler)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Etkin (yalnızca medya)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Ses paylaşımını destekler"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Etkin (yalnızca medya), yalnızca sol"</string> @@ -507,10 +490,10 @@ <string name="power_charging_duration" msgid="6127154952524919719">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tamamen şarj olmasına <xliff:g id="TIME">%2$s</xliff:g> kaldı"</string> <string name="power_charging_limited" msgid="8202147604844938236">"<xliff:g id="LEVEL">%1$s</xliff:g> - Şarj işlemi optimize edildi"</string> <string name="power_charging_future_paused" msgid="1809543660923642799">"<xliff:g id="LEVEL">%1$s</xliff:g> ‑ Şarj ediliyor"</string> - <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - Tamamen dolacağı zaman: <xliff:g id="TIME">%3$s</xliff:g>"</string> - <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> - Tamamen şarj olacağı zaman: <xliff:g id="TIME">%2$s</xliff:g>"</string> - <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"Tamamen şarj olacağı zaman: <xliff:g id="TIME">%1$s</xliff:g>"</string> - <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"Tamamen dolacağı zaman: <xliff:g id="TIME">%1$s</xliff:g>"</string> + <string name="power_fast_charging_duration_v2" msgid="3797735998640359490">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="STATUS">%2$s</xliff:g> - <xliff:g id="TIME">%3$s</xliff:g> içinde tamamen dolacak"</string> + <string name="power_charging_duration_v2" msgid="2938998284074003248">"<xliff:g id="LEVEL">%1$s</xliff:g> - <xliff:g id="TIME">%2$s</xliff:g> içinde tamamen şarj olacak"</string> + <string name="power_remaining_charging_duration_only_v2" msgid="5358176435722950193">"<xliff:g id="TIME">%1$s</xliff:g> içinde tamamen şarj olacak"</string> + <string name="power_remaining_fast_charging_duration_only_v2" msgid="6270950195810579563">"<xliff:g id="TIME">%1$s</xliff:g> içinde tamamen dolacak"</string> <string name="battery_info_status_unknown" msgid="268625384868401114">"Bilinmiyor"</string> <string name="battery_info_status_charging" msgid="4279958015430387405">"Şarj oluyor"</string> <string name="battery_info_status_charging_fast" msgid="8027559755902954885">"Hızlı şarj oluyor"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Cinsiyetsiz"</string> <string name="feminine" msgid="1529155595310784757">"Kadın"</string> <string name="masculine" msgid="4653978041013996303">"Erkek"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Sistem Güncellemeleri"</string> </resources> diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml index 807bc51b8fed..ecfa2c90049c 100644 --- a/packages/SettingsLib/res/values-uk/strings.xml +++ b/packages/SettingsLib/res/values-uk/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Лівий: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> заряду акумулятора"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Правий: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> заряду акумулятора"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Активовано"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Збережено"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Активовано (лише лівий)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Активовано (лише правий)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Активовано (лівий і правий)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Активне з’єднання (лише для мультимедіа). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> заряду акумулятора."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Активне з’єднання (лише для мультимедіа). Лівий: <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_battery_level_lea_support" msgid="5968584103507988820">"Підключено (підтримує надсилання аудіо). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> заряду акумулятора."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Підключено (підтримує надсилання аудіо). Лівий: <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Підключено (підтримує надсилання аудіо). Лівий: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> заряду акумулятора."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Підключено (підтримує надсилання аудіо). Правий: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> заряду акумулятора."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Підключено (підтримує надсилання аудіо)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Активно (лише для мультимедіа)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Підтримує надсилання аудіо"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Активно (лише для мультимедіа); лише лівий"</string> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Вибрати профіль"</string> <string name="category_personal" msgid="6236798763159385225">"Особисті"</string> <string name="category_work" msgid="4014193632325996115">"Робочі"</string> - <string name="category_private" msgid="4244892185452788977">"Приватні"</string> + <string name="category_private" msgid="4244892185452788977">"Приватний простір"</string> <string name="category_clone" msgid="1554511758987195974">"Копія профілю"</string> <string name="development_settings_title" msgid="140296922921597393">"Параметри розробника"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Увімкнути параметри розробника"</string> @@ -613,7 +596,7 @@ <string name="help_label" msgid="3528360748637781274">"Довідка й відгуки"</string> <string name="storage_category" msgid="2287342585424631813">"Пам\'ять"</string> <string name="shared_data_title" msgid="1017034836800864953">"Спільні дані"</string> - <string name="shared_data_summary" msgid="5516326713822885652">"Переглянути та змінити спільні дані"</string> + <string name="shared_data_summary" msgid="5516326713822885652">"Переглянути й змінити спільні дані"</string> <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Немає спільних даних для цього користувача."</string> <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Не вдалось отримати спільні дані. Повторіть спробу."</string> <string name="blob_id_text" msgid="8680078988996308061">"Ідентифікатор спільних даних: <xliff:g id="BLOB_ID">%d</xliff:g>"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Середній рід"</string> <string name="feminine" msgid="1529155595310784757">"Жіночий рід"</string> <string name="masculine" msgid="4653978041013996303">"Чоловічий рід"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Оновлення системи"</string> </resources> diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml index 80841622890b..ed0b1dffd086 100644 --- a/packages/SettingsLib/res/values-ur/strings.xml +++ b/packages/SettingsLib/res/values-ur/strings.xml @@ -96,10 +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">"فعال۔ 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> بیٹری۔"</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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">"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> بیٹری۔"</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"دائيں: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> بیٹری"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"فعال"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"محفوظ ہے"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"فعال ہے (صرف بایاں)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"فعال ہے (صرف دایاں)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"فعال ہے (بایاں اور دایاں)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"فعال (صرف میڈیا)۔ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> بیٹری۔"</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"فعال (صرف میڈیا)۔ 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> بیٹری۔"</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"منسلک ہے (آڈیو کے اشتراک کو سپورٹ کرتا ہے)۔ <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> بیٹری۔"</string> @@ -164,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> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"غیر واضح"</string> <string name="feminine" msgid="1529155595310784757">"مؤنث"</string> <string name="masculine" msgid="4653978041013996303">"مذکر"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"سسٹم اپ ڈیٹس"</string> </resources> diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml index 4415667cf144..104f27b9df85 100644 --- a/packages/SettingsLib/res/values-uz/strings.xml +++ b/packages/SettingsLib/res/values-uz/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"<xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g> ulandi (telefondan tashqari), batareya quvvati: <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> ulandi (mediadan tashqari), batareya quvvati: <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> ulandi (telefon yoki mediadan tashqari), batareya quvvati: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Faol. Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Faol. Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (L), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (R)."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Faol. L: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> quvvat"</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Faol. R: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> quvvat"</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Batareya quvvati: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Batareya: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (L), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (R)."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (chap)"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (oʻng)."</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Faol"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Saqlangan"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Faol (faqat chap)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Faol (faqat oʻng)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Faol (chap va oʻng)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Faol (faqat media uchun) Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Faol (faqat media uchun), quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (L), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (R)"</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Ulangan (audio yuborish mumkin), quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Ulangan (audio yuborish mumkin), quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> (L), <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> (R)"</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Ulangan (audio yuborish mumkin). Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (chap)."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Ulangan (audio yuborish mumkin). Quvvat: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> (oʻng)."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Ulangan (audio yuborish mumkin)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Faol (faqat media uchun)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Audio yuborishi mumkin"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Faol (faqat media uchun), faqat chap"</string> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"Profilni tanlang"</string> <string name="category_personal" msgid="6236798763159385225">"Shaxsiy"</string> <string name="category_work" msgid="4014193632325996115">"Ish"</string> - <string name="category_private" msgid="4244892185452788977">"Yopiq"</string> + <string name="category_private" msgid="4244892185452788977">"Maxfiy"</string> <string name="category_clone" msgid="1554511758987195974">"Nusxalash"</string> <string name="development_settings_title" msgid="140296922921597393">"Dasturchi sozlamalari"</string> <string name="development_settings_enable" msgid="4285094651288242183">"Dasturchi sozlamalarini yoqish"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Oʻrta"</string> <string name="feminine" msgid="1529155595310784757">"Ayollar uchun"</string> <string name="masculine" msgid="4653978041013996303">"Erkaklar uchun"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Tizim yangilanishi"</string> </resources> diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml index f33d9092b7c8..2ed847c6f448 100644 --- a/packages/SettingsLib/res/values-vi/strings.xml +++ b/packages/SettingsLib/res/values-vi/strings.xml @@ -94,44 +94,27 @@ <string name="bluetooth_connected_no_headset_battery_level" msgid="2661863370509206428">"Đã kết nối (không có điện thoại), mức pin <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">"Đã kết nối (không có phương tiện), mức pin <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">"Đã kết nối (không có điện thoại hoặc phương tiện), mức pin <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g><xliff:g id="ACTIVE_DEVICE">%2$s</xliff:g>"</string> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level" msgid="2685517576209066008">"Đang hoạt động. Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string> + <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Đang hoạt động. Bên trái: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> pin. Bên phải: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pin."</string> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Đang hoạt động. Tai trái: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Đang hoạt động. Tai phải: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"Mức pin <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Pin <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"Bên trái: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> pin. Bên phải: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pin."</string> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"Bên trái: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Bên phải: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Đang hoạt động"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Đã lưu"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Đang hoạt động (chỉ tai trái)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Đang hoạt động (chỉ tai phải)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Đang hoạt động (cả tai phải và tai trái)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Đang hoạt động (chỉ phát nội dung đa phương tiện). Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Đang hoạt động (chỉ phát nội dung đa phương tiện). Bên trái: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> pin. Bên phải: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pin."</string> + <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Đã kết nối (có hỗ trợ tính năng chia sẻ âm thanh). Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"Đã kết nối (có hỗ trợ tính năng chia sẻ âm thanh). Bên trái: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_0">%1$s</xliff:g> pin. Bên phải: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE_1">%2$s</xliff:g> pin."</string> + <string name="bluetooth_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"Đã kết nối (có hỗ trợ tính năng chia sẻ âm thanh). Bên trái: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"Đã kết nối (có hỗ trợ tính năng chia sẻ âm thanh). Bên phải: Còn <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> pin."</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"Đã kết nối (có hỗ trợ tính năng chia sẻ âm thanh)."</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"Đang hoạt động (chỉ phát nội dung đa phương tiện)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"Hỗ trợ tính năng chia sẻ âm thanh"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"Đang hoạt động (chỉ phát nội dung đa phương tiện), chỉ dùng tai nghe bên trái"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Vô tính"</string> <string name="feminine" msgid="1529155595310784757">"Giống cái"</string> <string name="masculine" msgid="4653978041013996303">"Giống đực"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Cập nhật hệ thống"</string> </resources> diff --git a/packages/SettingsLib/res/values-zh-rCN/arrays.xml b/packages/SettingsLib/res/values-zh-rCN/arrays.xml index 432dd5f829e2..939b4ec5f09f 100644 --- a/packages/SettingsLib/res/values-zh-rCN/arrays.xml +++ b/packages/SettingsLib/res/values-zh-rCN/arrays.xml @@ -257,7 +257,7 @@ <item msgid="1212561935004167943">"以绿色突出显示测试绘制命令"</item> </string-array> <string-array name="track_frame_time_entries"> - <item msgid="634406443901014984">"关闭"</item> + <item msgid="634406443901014984">"已关闭"</item> <item msgid="1288760936356000927">"在屏幕上显示为条形图"</item> <item msgid="5023908510820531131">"在 <xliff:g id="AS_TYPED_COMMAND">adb shell dumpsys gfxinfo</xliff:g> 中"</item> </string-array> diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml index 816ba8db172b..25ca0c214d69 100644 --- a/packages/SettingsLib/res/values-zh-rCN/strings.xml +++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"左侧电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"右侧电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"使用中"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"已保存的设备"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"使用中(仅左耳助听器)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"使用中(仅右耳助听器)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"使用中(左右耳助听器)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"使用中(仅限媒体)。电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"使用中(仅限媒体)。左侧电池电量为 <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_battery_level_lea_support" msgid="5968584103507988820">"已连接(支持音频分享)。电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"已连接(支持音频分享)。左侧电池电量为 <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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"已连接(支持音频分享)。左侧电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"已连接(支持音频分享)。右侧电池电量为 <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"已连接(支持音频分享)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"使用中(仅限媒体)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"支持音频分享"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"使用中(仅限媒体),仅左侧"</string> @@ -252,7 +235,7 @@ <string name="choose_profile" msgid="343803890897657450">"选择个人资料"</string> <string name="category_personal" msgid="6236798763159385225">"个人"</string> <string name="category_work" msgid="4014193632325996115">"工作"</string> - <string name="category_private" msgid="4244892185452788977">"私享"</string> + <string name="category_private" msgid="4244892185452788977">"私密"</string> <string name="category_clone" msgid="1554511758987195974">"克隆"</string> <string name="development_settings_title" msgid="140296922921597393">"开发者选项"</string> <string name="development_settings_enable" msgid="4285094651288242183">"启用开发者选项"</string> @@ -723,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> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"中性"</string> <string name="feminine" msgid="1529155595310784757">"阴性"</string> <string name="masculine" msgid="4653978041013996303">"阳性"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"系统更新"</string> </resources> diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml index b26efb23b17c..60c71e01e4a6 100644 --- a/packages/SettingsLib/res/values-zh-rHK/strings.xml +++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"左側:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> 電量"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"右側:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> 電量。"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"使用中"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"已儲存"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"使用中 (僅左側)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"使用中 (僅右側)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"使用中 (左右兩側)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"啟用 (只限媒體)。<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> 電量。"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"啟用 (只限媒體),左側:<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_battery_level_lea_support" msgid="5968584103507988820">"已連線 (支援音訊分享功能),<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> 電量。"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"已連線 (支援音訊分享功能),左側:<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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"已連線 (支援音訊分享功能)。左側:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> 電量。"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"已連線 (支援音訊分享功能)。右側:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> 電量。"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"已連線 (支援音訊分享功能)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"啟用 (只限媒體)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"支援音訊分享功能"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"左側啟用 (只限媒體)"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"中性"</string> <string name="feminine" msgid="1529155595310784757">"女性"</string> <string name="masculine" msgid="4653978041013996303">"男性"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"系統更新"</string> </resources> diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml index 149022ca8680..61597363c377 100644 --- a/packages/SettingsLib/res/values-zh-rTW/strings.xml +++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml @@ -94,44 +94,27 @@ <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> - <!-- no translation found for bluetooth_active_battery_level (2685517576209066008) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered (4961338936672922617) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <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_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> - <!-- no translation found for bluetooth_battery_level_untethered (1616774716076301755) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left (5725764679536058365) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right (8377995536997790142) --> - <skip /> + <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> + <string name="bluetooth_battery_level_untethered_left" msgid="5725764679536058365">"左側電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> + <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"右側電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"使用中"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"已儲存"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level (7772517511061834073) --> - <skip /> - <!-- no translation found for bluetooth_active_media_only_battery_level_untethered (7444753133664620926) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_lea_support (5968584103507988820) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_lea_support (803110681688633362) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_left_lea_support (7707464334346454950) --> - <skip /> - <!-- no translation found for bluetooth_battery_level_untethered_right_lea_support (8941549024377771038) --> - <skip /> - <!-- no translation found for bluetooth_no_battery_level_lea_support (5721725041048434075) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"使用中 (僅左側)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"使用中 (僅右側)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"使用中 (左右兩側)"</string> + <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"已啟用 (僅限媒體)。電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> + <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"已啟用 (僅限媒體)。左側電量:<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_battery_level_lea_support" msgid="5968584103507988820">"已連線 (支援音訊分享)。電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> + <string name="bluetooth_battery_level_untethered_lea_support" msgid="803110681688633362">"已連線 (支援音訊分享)。左側電量:<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_battery_level_untethered_left_lea_support" msgid="7707464334346454950">"已連線 (支援音訊分享)。左側電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> + <string name="bluetooth_battery_level_untethered_right_lea_support" msgid="8941549024377771038">"已連線 (支援音訊分享)。右側電量:<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>。"</string> + <string name="bluetooth_no_battery_level_lea_support" msgid="5721725041048434075">"已連線 (支援音訊分享)"</string> <string name="bluetooth_active_media_only_no_battery_level" msgid="71106861912593126">"啟用 (僅限媒體)"</string> <string name="bluetooth_saved_device_lea_support" msgid="7231323139968285768">"支援音訊分享"</string> <string name="bluetooth_hearing_aid_media_only_left_active" msgid="1632152540901488645">"左側啟用 (僅限媒體)"</string> @@ -738,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"中性"</string> <string name="feminine" msgid="1529155595310784757">"陰性"</string> <string name="masculine" msgid="4653978041013996303">"陽性"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"系統更新"</string> </resources> diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml index 84488146ddce..0200c01b4b49 100644 --- a/packages/SettingsLib/res/values-zu/strings.xml +++ b/packages/SettingsLib/res/values-zu/strings.xml @@ -96,10 +96,8 @@ <string name="bluetooth_connected_no_headset_no_a2dp_battery_level" msgid="8477440576953067242">"Ixhunyiwe (ayikho ifoni noma imidiya), ibhethri ngu-<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">"Iyasebenza. <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ibhethri."</string> <string name="bluetooth_active_battery_level_untethered" msgid="4961338936672922617">"Iyasebenza. 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> ibhethri."</string> - <!-- no translation found for bluetooth_active_battery_level_untethered_left (2895644748625343977) --> - <skip /> - <!-- no translation found for bluetooth_active_battery_level_untethered_right (7407517998880370179) --> - <skip /> + <string name="bluetooth_active_battery_level_untethered_left" msgid="2895644748625343977">"Iyasebenza. L: U-<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> webhethri"</string> + <string name="bluetooth_active_battery_level_untethered_right" msgid="7407517998880370179">"Iyasebenza. R: U-<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> webhethri"</string> <string name="bluetooth_battery_level" msgid="2893696778200201555">"<xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ibhethri"</string> <string name="tv_bluetooth_battery_level" msgid="8786353985605532846">"Ibhethri <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g>"</string> <string name="bluetooth_battery_level_untethered" msgid="1616774716076301755">"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> ibhethri."</string> @@ -107,12 +105,9 @@ <string name="bluetooth_battery_level_untethered_right" msgid="8377995536997790142">"Kwesokudla: <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ibhethri"</string> <string name="bluetooth_active_no_battery_level" msgid="4155462233006205630">"Iyasebenza"</string> <string name="bluetooth_saved_device" msgid="4895871321722311428">"Ilondoloziwe"</string> - <!-- no translation found for bluetooth_hearing_aid_left_active (8330226430756799572) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_right_active (2244728507170385397) --> - <skip /> - <!-- no translation found for bluetooth_hearing_aid_left_and_right_active (4294571497939983181) --> - <skip /> + <string name="bluetooth_hearing_aid_left_active" msgid="8330226430756799572">"Iyasebenza (ngakwesokunxele kuphela)"</string> + <string name="bluetooth_hearing_aid_right_active" msgid="2244728507170385397">"Iyasebenza (ngakwesokudla kuphela)"</string> + <string name="bluetooth_hearing_aid_left_and_right_active" msgid="4294571497939983181">"Iyasebenza (ngakwesokunxele nakwesokudla)"</string> <string name="bluetooth_active_media_only_battery_level" msgid="7772517511061834073">"Iyasebenza (imidiya kuphela). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ibhethri."</string> <string name="bluetooth_active_media_only_battery_level_untethered" msgid="7444753133664620926">"Iyasebenza (imidiya kuphela). 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> ibhethri."</string> <string name="bluetooth_battery_level_lea_support" msgid="5968584103507988820">"Ixhunyiwe (isekela ukwabelana ngokuqoshiwe). <xliff:g id="BATTERY_LEVEL_AS_PERCENTAGE">%1$s</xliff:g> ibhethri."</string> @@ -726,4 +721,5 @@ <string name="neuter" msgid="2075249330106127310">"Okungenabulili"</string> <string name="feminine" msgid="1529155595310784757">"Okwabesifazane"</string> <string name="masculine" msgid="4653978041013996303">"Okwabesilisa"</string> + <string name="system_update_settings_list_item_title" msgid="6618098383615432484">"Izibuyekezo Zesistimu"</string> </resources> diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index adbfc72cd684..363045ec1d83 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -214,6 +214,10 @@ <string name="bluetooth_battery_level_untethered_left">Left: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery</string> <!-- Connected devices settings. Message when Bluetooth is connected but not in use, showing remote device battery level for the right part of the untethered headset. [CHAR LIMIT=NONE] --> <string name="bluetooth_battery_level_untethered_right">Right: <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g> battery</string> + <!-- Connected devices settings. Message when Bluetooth is connected, showing remote device battery level for the left part of the untethered headset. [CHAR LIMIT=NONE] --> + <string name="tv_bluetooth_battery_level_untethered_left">Left <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string> + <!-- Connected devices settings. Message when Bluetooth is connected, showing remote device battery level for the right part of the untethered headset. [CHAR LIMIT=NONE] --> + <string name="tv_bluetooth_battery_level_untethered_right">Right <xliff:g id="battery_level_as_percentage" example="25%">%1$s</xliff:g></string> <!-- Connected devices settings. Message when Bluetooth is connected and active but no battery information, showing remote device status. [CHAR LIMIT=NONE] --> <string name="bluetooth_active_no_battery_level">Active</string> <!-- Connected devices settings. Message shown when bluetooth device is disconnected but is a known, previously connected device [CHAR LIMIT=NONE] --> @@ -1712,7 +1716,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/applications/RecentAppOpsAccess.java b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java index 57bde56b4c04..c3651423b487 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/RecentAppOpsAccess.java @@ -128,7 +128,7 @@ public class RecentAppOpsAccess { final long now = mClock.millis(); final UserManager um = mContext.getSystemService(UserManager.class); final List<UserHandle> profiles = um.getUserProfiles(); - ArrayMap<UserHandle, Boolean> shouldIncludeAppsByUsers = new ArrayMap<>(); + ArrayMap<UserHandle, Boolean> shouldHideAppsByUsers = new ArrayMap<>(); for (int i = 0; i < appOpsCount; ++i) { AppOpsManager.PackageOps ops = appOps.get(i); @@ -136,13 +136,13 @@ public class RecentAppOpsAccess { int uid = ops.getUid(); UserHandle user = UserHandle.getUserHandleForUid(uid); - if (!shouldIncludeAppsByUsers.containsKey(user)) { - shouldIncludeAppsByUsers.put(user, shouldHideUser(um, user)); + if (!shouldHideAppsByUsers.containsKey(user)) { + shouldHideAppsByUsers.put(user, shouldHideUser(um, user)); } // Don't show apps belonging to background users except for profiles that shouldn't // be shown in quiet mode. - if (!profiles.contains(user) || !shouldIncludeAppsByUsers.get(user)) { + if (!profiles.contains(user) || shouldHideAppsByUsers.get(user)) { continue; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java index 3dffb275f917..721e7b93fd32 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; @@ -279,6 +276,14 @@ public class BluetoothUtils { if (isUntetheredHeadset(bluetoothDevice)) { return true; } + if (Flags.enableDeterminingAdvancedDetailsHeaderWithMetadata()) { + // A FastPair device that use advanced details header must have METADATA_MAIN_ICON + if (getUriMetaData(bluetoothDevice, BluetoothDevice.METADATA_MAIN_ICON) != null) { + Log.d(TAG, "isAdvancedDetailsHeader is true with main icon uri"); + return true; + } + return false; + } // The metadata is for Android S String deviceType = getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); @@ -305,12 +310,15 @@ public class BluetoothUtils { if (isUntetheredHeadset(bluetoothDevice)) { return true; } - // The metadata is for Android S - String deviceType = - getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); - if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) { - Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device "); - return true; + if (!Flags.enableDeterminingAdvancedDetailsHeaderWithMetadata()) { + // The METADATA_IS_UNTETHERED_HEADSET of an untethered FastPair headset is always true, + // so there's no need to check the device type. + String deviceType = + getStringMetaData(bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE); + if (TextUtils.equals(deviceType, BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET)) { + Log.d(TAG, "isAdvancedUntetheredDevice: is untethered device"); + return true; + } } return false; } @@ -740,14 +748,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 +762,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 a7b7da598d12..0fec61c5affe 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; @@ -649,6 +650,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> for (CachedBluetoothDevice cbd : mMemberDevices) { cbd.setName(name); } + if (mSubDevice != null) { + mSubDevice.setName(name); + } } /** @@ -1439,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)) { @@ -1482,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); } } } @@ -1495,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( @@ -1507,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); } @@ -1524,7 +1532,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> // the left. if (leftBattery >= 0) { String left = res.getString( - R.string.bluetooth_battery_level_untethered_left, + R.string.tv_bluetooth_battery_level_untethered_left, Utils.formatPercentage(leftBattery)); addBatterySpan(spannableBuilder, left, isBatteryLow(leftBattery, BluetoothDevice.METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD), @@ -1535,7 +1543,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> spannableBuilder.append(" "); } String right = res.getString( - R.string.bluetooth_battery_level_untethered_right, + R.string.tv_bluetooth_battery_level_untethered_right, Utils.formatPercentage(rightBattery)); addBatterySpan(spannableBuilder, right, isBatteryLow(rightBattery, BluetoothDevice.METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD), @@ -1550,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 (rightBattery < 0 && rightDevice != null) { - rightBattery = rightDevice.getBatteryLevel(); + if (isLeftDeviceConnected) { + return shouldShowLeftBattery + ? R.string.bluetooth_active_battery_level_untethered_left + : R.string.bluetooth_hearing_aid_left_active; } - - 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, @@ -1629,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/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index 4e52c77f27b4..cb6a93002ea7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -176,6 +176,22 @@ public class CachedBluetoothDeviceManager { } /** + * Sync device status of the pair of the hearing aid if needed. + * + * @param device the remote device + */ + public synchronized void syncDeviceWithinHearingAidSetIfNeeded(CachedBluetoothDevice device, + int state, int profileId) { + if (profileId == BluetoothProfile.HAP_CLIENT + || profileId == BluetoothProfile.HEARING_AID + || profileId == BluetoothProfile.CSIP_SET_COORDINATOR) { + if (state == BluetoothProfile.STATE_CONNECTED) { + mHearingAidDeviceManager.syncDeviceIfNeeded(device); + } + } + } + + /** * Search for existing sub device {@link CachedBluetoothDevice}. * * @param device the address of the Bluetooth device diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java index 1069b715d946..ed964a9d0f40 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java @@ -15,6 +15,8 @@ */ package com.android.settingslib.bluetooth; +import android.bluetooth.BluetoothCsipSetCoordinator; +import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothLeAudio; import android.bluetooth.BluetoothProfile; @@ -98,6 +100,7 @@ public class HearingAidDeviceManager { // device. if (hearingAidDevice != null) { hearingAidDevice.setSubDevice(newDevice); + newDevice.setName(hearingAidDevice.getName()); return true; } } @@ -108,6 +111,10 @@ public class HearingAidDeviceManager { return hiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID; } + private boolean isValidGroupId(int groupId) { + return groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID; + } + private CachedBluetoothDevice getCachedDevice(long hiSyncId) { for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); @@ -258,6 +265,27 @@ public class HearingAidDeviceManager { } } + void syncDeviceIfNeeded(CachedBluetoothDevice device) { + final LocalBluetoothProfileManager profileManager = mBtManager.getProfileManager(); + final HapClientProfile hap = profileManager.getHapClientProfile(); + // Sync preset if device doesn't support synchronization on the remote side + if (hap != null && !hap.supportsSynchronizedPresets(device.getDevice())) { + final CachedBluetoothDevice mainDevice = findMainDevice(device); + if (mainDevice != null) { + int mainPresetIndex = hap.getActivePresetIndex(mainDevice.getDevice()); + int presetIndex = hap.getActivePresetIndex(device.getDevice()); + if (mainPresetIndex != BluetoothHapClient.PRESET_INDEX_UNAVAILABLE + && mainPresetIndex != presetIndex) { + if (DEBUG) { + Log.d(TAG, "syncing preset from " + presetIndex + "->" + + mainPresetIndex + ", device=" + device); + } + hap.selectPreset(device.getDevice(), mainPresetIndex); + } + } + } + } + private void setAudioRoutingConfig(CachedBluetoothDevice device) { AudioDeviceAttributes hearingDeviceAttributes = mRoutingHelper.getMatchedHearingDeviceAttributes(device); @@ -326,7 +354,19 @@ public class HearingAidDeviceManager { } CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) { + if (device == null || mCachedDevices == null) { + return null; + } + for (CachedBluetoothDevice cachedDevice : mCachedDevices) { + if (isValidGroupId(cachedDevice.getGroupId())) { + Set<CachedBluetoothDevice> memberSet = cachedDevice.getMemberDevice(); + for (CachedBluetoothDevice memberDevice : memberSet) { + if (memberDevice != null && memberDevice.equals(device)) { + return cachedDevice; + } + } + } if (isValidHiSyncId(cachedDevice.getHiSyncId())) { CachedBluetoothDevice subDevice = cachedDevice.getSubDevice(); if (subDevice != null && subDevice.equals(device)) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java index 8e3df8bcc2dd..2de2174a404b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtils.java @@ -48,17 +48,15 @@ public final class HearingAidStatsLogUtils { private static final String BT_HEARING_AIDS_PAIRED_HISTORY = "bt_hearing_aids_paired_history"; private static final String BT_HEARING_AIDS_CONNECTED_HISTORY = "bt_hearing_aids_connected_history"; - private static final String BT_HEARING_DEVICES_PAIRED_HISTORY = + private static final String BT_HEARABLE_DEVICES_PAIRED_HISTORY = "bt_hearing_devices_paired_history"; - private static final String BT_HEARING_DEVICES_CONNECTED_HISTORY = + private static final String BT_HEARABLE_DEVICES_CONNECTED_HISTORY = "bt_hearing_devices_connected_history"; - private static final String BT_HEARING_USER_CATEGORY = "bt_hearing_user_category"; - private static final String HISTORY_RECORD_DELIMITER = ","; static final String CATEGORY_HEARING_AIDS = "A11yHearingAidsUser"; static final String CATEGORY_NEW_HEARING_AIDS = "A11yNewHearingAidsUser"; - static final String CATEGORY_HEARING_DEVICES = "A11yHearingDevicesUser"; - static final String CATEGORY_NEW_HEARING_DEVICES = "A11yNewHearingDevicesUser"; + static final String CATEGORY_HEARABLE_DEVICES = "A11yHearingDevicesUser"; + static final String CATEGORY_NEW_HEARABLE_DEVICES = "A11yNewHearingDevicesUser"; static final int PAIRED_HISTORY_EXPIRED_DAY = 30; static final int CONNECTED_HISTORY_EXPIRED_DAY = 7; @@ -73,14 +71,14 @@ public final class HearingAidStatsLogUtils { HistoryType.TYPE_UNKNOWN, HistoryType.TYPE_HEARING_AIDS_PAIRED, HistoryType.TYPE_HEARING_AIDS_CONNECTED, - HistoryType.TYPE_HEARING_DEVICES_PAIRED, - HistoryType.TYPE_HEARING_DEVICES_CONNECTED}) + HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, + HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED}) public @interface HistoryType { int TYPE_UNKNOWN = -1; int TYPE_HEARING_AIDS_PAIRED = 0; int TYPE_HEARING_AIDS_CONNECTED = 1; - int TYPE_HEARING_DEVICES_PAIRED = 2; - int TYPE_HEARING_DEVICES_CONNECTED = 3; + int TYPE_HEARABLE_DEVICES_PAIRED = 2; + int TYPE_HEARABLE_DEVICES_CONNECTED = 3; } private static final HashMap<String, Integer> sDeviceAddressToBondEntryMap = new HashMap<>(); @@ -127,8 +125,8 @@ public final class HearingAidStatsLogUtils { } /** - * Updates corresponding history if we found the device is a hearing device after profile state - * changed. + * Updates corresponding history if we found the device is a hearing related device after + * profile state changed. * * @param context the request context * @param cachedDevice the remote device @@ -148,7 +146,7 @@ public final class HearingAidStatsLogUtils { } else if (cachedDevice.getProfiles().stream().anyMatch( p -> (p instanceof A2dpSinkProfile || p instanceof HeadsetProfile))) { HearingAidStatsLogUtils.addCurrentTimeToHistory(context, - HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED); + HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED); } removeFromJustBonded(cachedDevice.getAddress()); } @@ -161,7 +159,7 @@ public final class HearingAidStatsLogUtils { HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_CONNECTED); } else if (profile instanceof A2dpSinkProfile || profile instanceof HeadsetProfile) { HearingAidStatsLogUtils.addCurrentTimeToHistory(context, - HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED); + HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED); } } } @@ -169,18 +167,13 @@ public final class HearingAidStatsLogUtils { /** * Returns the user category if the user is already categorized. Otherwise, checks the * history and sees if the user is categorized as one of {@link #CATEGORY_HEARING_AIDS}, - * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARING_DEVICES}, and - * {@link #CATEGORY_NEW_HEARING_DEVICES}. + * {@link #CATEGORY_NEW_HEARING_AIDS}, {@link #CATEGORY_HEARABLE_DEVICES}, and + * {@link #CATEGORY_NEW_HEARABLE_DEVICES}. * * @param context the request context * @return the category which user belongs to */ public static synchronized String getUserCategory(Context context) { - String userCategory = getSharedPreferences(context).getString(BT_HEARING_USER_CATEGORY, ""); - if (!userCategory.isEmpty()) { - return userCategory; - } - LinkedList<Long> hearingAidsConnectedHistory = getHistory(context, HistoryType.TYPE_HEARING_AIDS_CONNECTED); if (hearingAidsConnectedHistory != null @@ -192,29 +185,29 @@ public final class HearingAidStatsLogUtils { // will be categorized as CATEGORY_HEARING_AIDS. if (hearingAidsPairedHistory != null && hearingAidsPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) { - userCategory = CATEGORY_NEW_HEARING_AIDS; + return CATEGORY_NEW_HEARING_AIDS; } else { - userCategory = CATEGORY_HEARING_AIDS; + return CATEGORY_HEARING_AIDS; } } - LinkedList<Long> hearingDevicesConnectedHistory = getHistory(context, - HistoryType.TYPE_HEARING_DEVICES_CONNECTED); - if (hearingDevicesConnectedHistory != null - && hearingDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) { - LinkedList<Long> hearingDevicesPairedHistory = getHistory(context, - HistoryType.TYPE_HEARING_DEVICES_PAIRED); + LinkedList<Long> hearableDevicesConnectedHistory = getHistory(context, + HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED); + if (hearableDevicesConnectedHistory != null + && hearableDevicesConnectedHistory.size() >= VALID_CONNECTED_EVENT_COUNT) { + LinkedList<Long> hearableDevicesPairedHistory = getHistory(context, + HistoryType.TYPE_HEARABLE_DEVICES_PAIRED); // Since paired history will be cleared after 30 days. If there's any record within 30 - // days, the user will be categorized as CATEGORY_NEW_HEARING_DEVICES. Otherwise, the - // user will be categorized as CATEGORY_HEARING_DEVICES. - if (hearingDevicesPairedHistory != null - && hearingDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) { - userCategory = CATEGORY_NEW_HEARING_DEVICES; + // days, the user will be categorized as CATEGORY_NEW_HEARABLE_DEVICES. Otherwise, the + // user will be categorized as CATEGORY_HEARABLE_DEVICES. + if (hearableDevicesPairedHistory != null + && hearableDevicesPairedHistory.size() >= VALID_PAIRED_EVENT_COUNT) { + return CATEGORY_NEW_HEARABLE_DEVICES; } else { - userCategory = CATEGORY_HEARING_DEVICES; + return CATEGORY_HEARABLE_DEVICES; } } - return userCategory; + return ""; } /** @@ -245,7 +238,7 @@ public final class HearingAidStatsLogUtils { } /** - * Adds current timestamp into BT hearing devices related history. + * Adds current timestamp into BT hearing related devices history. * @param context the request context * @param type the type of history to store the data. See {@link HistoryType}. */ @@ -279,13 +272,13 @@ public final class HearingAidStatsLogUtils { static synchronized LinkedList<Long> getHistory(Context context, @HistoryType int type) { String spName = HISTORY_TYPE_TO_SP_NAME_MAPPING.get(type); if (BT_HEARING_AIDS_PAIRED_HISTORY.equals(spName) - || BT_HEARING_DEVICES_PAIRED_HISTORY.equals(spName)) { + || BT_HEARABLE_DEVICES_PAIRED_HISTORY.equals(spName)) { LinkedList<Long> history = convertToHistoryList( getSharedPreferences(context).getString(spName, "")); removeRecordsBeforeDay(history, PAIRED_HISTORY_EXPIRED_DAY); return history; } else if (BT_HEARING_AIDS_CONNECTED_HISTORY.equals(spName) - || BT_HEARING_DEVICES_CONNECTED_HISTORY.equals(spName)) { + || BT_HEARABLE_DEVICES_CONNECTED_HISTORY.equals(spName)) { LinkedList<Long> history = convertToHistoryList( getSharedPreferences(context).getString(spName, "")); removeRecordsBeforeDay(history, CONNECTED_HISTORY_EXPIRED_DAY); @@ -352,9 +345,9 @@ public final class HearingAidStatsLogUtils { HISTORY_TYPE_TO_SP_NAME_MAPPING.put( HistoryType.TYPE_HEARING_AIDS_CONNECTED, BT_HEARING_AIDS_CONNECTED_HISTORY); HISTORY_TYPE_TO_SP_NAME_MAPPING.put( - HistoryType.TYPE_HEARING_DEVICES_PAIRED, BT_HEARING_DEVICES_PAIRED_HISTORY); + HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, BT_HEARABLE_DEVICES_PAIRED_HISTORY); HISTORY_TYPE_TO_SP_NAME_MAPPING.put( - HistoryType.TYPE_HEARING_DEVICES_CONNECTED, BT_HEARING_DEVICES_CONNECTED_HISTORY); + HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, BT_HEARABLE_DEVICES_CONNECTED_HISTORY); } private HearingAidStatsLogUtils() {} } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 4055986e8a57..8dfeb559a8b5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -408,6 +408,8 @@ public class LocalBluetoothProfileManager { boolean needDispatchProfileConnectionState = true; if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID || cachedDevice.getGroupId() != BluetoothCsipSetCoordinator.GROUP_ID_INVALID) { + mDeviceManager.syncDeviceWithinHearingAidSetIfNeeded(cachedDevice, newState, + mProfile.getProfileId()); needDispatchProfileConnectionState = !mDeviceManager .onProfileConnectionStateChangedIfProcessed(cachedDevice, newState, mProfile.getProfileId()); 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/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index eae58adb5381..b7758de0e19c 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -40,6 +40,7 @@ import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; import static android.media.MediaRoute2Info.TYPE_USB_HEADSET; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; +import static android.media.session.MediaController.PlaybackInfo; import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; @@ -51,6 +52,8 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.session.MediaController; +import android.media.session.MediaSession; import android.os.Build; import android.os.UserHandle; import android.text.TextUtils; @@ -135,19 +138,28 @@ public abstract class InfoMediaManager { @NonNull protected final UserHandle mUserHandle; private final Collection<MediaDeviceCallback> mCallbacks = new CopyOnWriteArrayList<>(); private MediaDevice mCurrentConnectedDevice; + private MediaController mMediaController; + private PlaybackInfo mLastKnownPlaybackInfo; private final LocalBluetoothManager mBluetoothManager; private final Map<String, RouteListingPreference.Item> mPreferenceItemMap = new ConcurrentHashMap<>(); + private final MediaController.Callback mMediaControllerCallback = new MediaControllerCallback(); + /* package */ InfoMediaManager( @NonNull Context context, @NonNull String packageName, @NonNull UserHandle userHandle, - @NonNull LocalBluetoothManager localBluetoothManager) { + @NonNull LocalBluetoothManager localBluetoothManager, + @Nullable MediaController mediaController) { mContext = context; mBluetoothManager = localBluetoothManager; mPackageName = packageName; mUserHandle = userHandle; + mMediaController = mediaController; + if (mediaController != null) { + mLastKnownPlaybackInfo = mediaController.getPlaybackInfo(); + } } /** @@ -159,12 +171,19 @@ public abstract class InfoMediaManager { * speakers, as opposed to app-specific routing (for example, casting to another device). * @param userHandle The {@link UserHandle} of the user on which the app to control is running, * or null if the caller does not need app-specific routing (see {@code packageName}). + * @param token The token of the associated {@link MediaSession} for which to do media routing. */ public static InfoMediaManager createInstance( Context context, @Nullable String packageName, @Nullable UserHandle userHandle, - LocalBluetoothManager localBluetoothManager) { + LocalBluetoothManager localBluetoothManager, + @Nullable MediaSession.Token token) { + MediaController mediaController = null; + + if (Flags.usePlaybackInfoForRoutingControls() && token != null) { + mediaController = new MediaController(context, token); + } // The caller is only interested in system routes (headsets, built-in speakers, etc), and is // not interested in a specific app's routing. The media routing APIs still require a @@ -180,16 +199,16 @@ public abstract class InfoMediaManager { if (Flags.useMediaRouter2ForInfoMediaManager()) { try { return new RouterInfoMediaManager( - context, packageName, userHandle, localBluetoothManager); + context, packageName, userHandle, localBluetoothManager, mediaController); } catch (PackageNotAvailableException ex) { // TODO: b/293578081 - Propagate this exception to callers for proper handling. Log.w(TAG, "Returning a no-op InfoMediaManager for package " + packageName); return new NoOpInfoMediaManager( - context, packageName, userHandle, localBluetoothManager); + context, packageName, userHandle, localBluetoothManager, mediaController); } } else { return new ManagerInfoMediaManager( - context, packageName, userHandle, localBluetoothManager); + context, packageName, userHandle, localBluetoothManager, mediaController); } } @@ -310,6 +329,9 @@ public abstract class InfoMediaManager { if (wasEmpty) { mMediaDevices.clear(); registerRouter(); + if (mMediaController != null) { + mMediaController.registerCallback(mMediaControllerCallback); + } updateRouteListingPreference(); refreshDevices(); } @@ -323,6 +345,9 @@ public abstract class InfoMediaManager { */ public final void unregisterCallback(@NonNull MediaDeviceCallback callback) { if (mCallbacks.remove(callback) && mCallbacks.isEmpty()) { + if (mMediaController != null) { + mMediaController.unregisterCallback(mMediaControllerCallback); + } unregisterRouter(); } } @@ -389,7 +414,34 @@ public abstract class InfoMediaManager { private RoutingSessionInfo getActiveRoutingSession() { // List is never empty. final List<RoutingSessionInfo> sessions = getRoutingSessionsForPackage(); - return sessions.get(sessions.size() - 1); + RoutingSessionInfo activeSession = sessions.get(sessions.size() - 1); + + // Logic from MediaRouter2Manager#getRoutingSessionForMediaController + if (!Flags.usePlaybackInfoForRoutingControls() || mMediaController == null) { + return activeSession; + } + + PlaybackInfo playbackInfo = mMediaController.getPlaybackInfo(); + if (playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_LOCAL) { + // Return system session. + return sessions.get(0); + } + + // For PLAYBACK_TYPE_REMOTE. + String volumeControlId = playbackInfo.getVolumeControlId(); + for (RoutingSessionInfo session : sessions) { + if (TextUtils.equals(volumeControlId, session.getId())) { + return session; + } + // Workaround for provider not being able to know the unique session ID. + if (TextUtils.equals(volumeControlId, session.getOriginalId()) + && TextUtils.equals( + mMediaController.getPackageName(), session.getOwnerPackageName())) { + return session; + } + } + + return activeSession; } boolean isRoutingSessionAvailableForVolumeControl() { @@ -808,4 +860,23 @@ public abstract class InfoMediaManager { } } } + + private final class MediaControllerCallback extends MediaController.Callback { + @Override + public void onSessionDestroyed() { + mMediaController = null; + refreshDevices(); + } + + @Override + public void onAudioInfoChanged(@NonNull PlaybackInfo info) { + if (info.getPlaybackType() != mLastKnownPlaybackInfo.getPlaybackType() + || !TextUtils.equals( + info.getVolumeControlId(), + mLastKnownPlaybackInfo.getVolumeControlId())) { + refreshDevices(); + } + mLastKnownPlaybackInfo = info; + } + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java index 473c62704b8b..cfa825bbb1c4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java @@ -149,7 +149,11 @@ public class LocalMediaManager implements BluetoothCallback { // TODO: b/321969740 - Take the userHandle as a parameter and pass it through. The // package name is not sufficient to unambiguously identify an app. InfoMediaManager.createInstance( - context, packageName, /* userHandle */ null, mLocalBluetoothManager); + context, + packageName, + /* userHandle */ null, + mLocalBluetoothManager, + /* token */ null); } /** diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java index d621751a2c29..82b197682459 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java @@ -21,6 +21,7 @@ import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.session.MediaController; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -55,8 +56,9 @@ public class ManagerInfoMediaManager extends InfoMediaManager { Context context, @NonNull String packageName, @NonNull UserHandle userHandle, - LocalBluetoothManager localBluetoothManager) { - super(context, packageName, userHandle, localBluetoothManager); + LocalBluetoothManager localBluetoothManager, + @Nullable MediaController mediaController) { + super(context, packageName, userHandle, localBluetoothManager, mediaController); mRouterManager = MediaRouter2Manager.getInstance(context); } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java index d2b018cd2299..2c7ec9302117 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java @@ -20,6 +20,7 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.session.MediaController; import android.os.UserHandle; import androidx.annotation.NonNull; @@ -60,8 +61,9 @@ import java.util.List; Context context, @NonNull String packageName, @NonNull UserHandle userHandle, - LocalBluetoothManager localBluetoothManager) { - super(context, packageName, userHandle, localBluetoothManager); + LocalBluetoothManager localBluetoothManager, + @Nullable MediaController mediaController) { + super(context, packageName, userHandle, localBluetoothManager, mediaController); } @Override diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java index 045c60dd1514..6571dd7ba398 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java @@ -25,6 +25,7 @@ import android.media.MediaRouter2Manager; import android.media.RouteDiscoveryPreference; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.media.session.MediaController; import android.os.UserHandle; import android.text.TextUtils; @@ -71,9 +72,10 @@ public final class RouterInfoMediaManager extends InfoMediaManager { Context context, @NonNull String packageName, @NonNull UserHandle userHandle, - LocalBluetoothManager localBluetoothManager) + LocalBluetoothManager localBluetoothManager, + @Nullable MediaController mediaController) throws PackageNotAvailableException { - super(context, packageName, userHandle, localBluetoothManager); + super(context, packageName, userHandle, localBluetoothManager, mediaController); MediaRouter2 router = null; diff --git a/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt b/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt index 68f471dd4e4f..d198136447a5 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt +++ b/packages/SettingsLib/src/com/android/settingslib/media/session/MediaSessionManagerExt.kt @@ -45,14 +45,13 @@ val MediaSessionManager.activeMediaChanges: Flow<List<MediaController>?> .buffer(capacity = Channel.CONFLATED) /** [Flow] for [MediaSessionManager.RemoteSessionCallback]. */ -val MediaSessionManager.remoteSessionChanges: Flow<MediaSession.Token?> +val MediaSessionManager.defaultRemoteSessionChanged: Flow<MediaSession.Token?> get() = callbackFlow { val callback = object : MediaSessionManager.RemoteSessionCallback { - override fun onVolumeChanged(sessionToken: MediaSession.Token, flags: Int) { - launch { send(sessionToken) } - } + override fun onVolumeChanged(sessionToken: MediaSession.Token, flags: Int) = + Unit override fun onDefaultRemoteSessionChanged( sessionToken: MediaSession.Token? diff --git a/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt new file mode 100644 index 000000000000..d69c87b318e2 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/satellite/SatelliteDialogUtils.kt @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.satellite + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.OutcomeReceiver +import android.telephony.satellite.SatelliteManager +import android.util.Log +import android.view.WindowManager +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.android.settingslib.wifi.WifiUtils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.Default +import kotlinx.coroutines.Job +import kotlinx.coroutines.asExecutor +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withContext +import java.util.concurrent.ExecutionException +import java.util.concurrent.TimeoutException +import kotlin.coroutines.resume + +/** A util for Satellite dialog */ +object SatelliteDialogUtils { + + /** + * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and + * Wifi during the satellite mode is on. + */ + @JvmStatic + fun mayStartSatelliteWarningDialog( + context: Context, + lifecycleOwner: LifecycleOwner, + type: Int, + allowClick: (isAllowed: Boolean) -> Unit + ): Job { + return mayStartSatelliteWarningDialog( + context, lifecycleOwner.lifecycleScope, type, allowClick) + } + + /** + * Uses to start Satellite dialog to prevent users from using the BT, Airplane Mode, and + * Wifi during the satellite mode is on. + */ + @JvmStatic + fun mayStartSatelliteWarningDialog( + context: Context, + coroutineScope: CoroutineScope, + type: Int, + allowClick: (isAllowed: Boolean) -> Unit + ): Job = + coroutineScope.launch { + var isSatelliteModeOn = false + try { + isSatelliteModeOn = requestIsEnabled(context) + } catch (e: InterruptedException) { + Log.w(TAG, "Error to get satellite status : $e") + } catch (e: ExecutionException) { + Log.w(TAG, "Error to get satellite status : $e") + } catch (e: TimeoutException) { + Log.w(TAG, "Error to get satellite status : $e") + } + + if (isSatelliteModeOn) { + startSatelliteWarningDialog(context, type) + } + withContext(Dispatchers.Main) { + allowClick(!isSatelliteModeOn) + } + } + + private fun startSatelliteWarningDialog(context: Context, type: Int) { + context.startActivity(Intent(Intent.ACTION_MAIN).apply { + component = ComponentName( + "com.android.settings", + "com.android.settings.network.SatelliteWarningDialogActivity" + ) + putExtra(WifiUtils.DIALOG_WINDOW_TYPE, + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) + putExtra(EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, type) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) + }) + } + + /** + * Checks if the satellite modem is enabled. + * + * @param executor The executor to run the asynchronous operation on + * @return A ListenableFuture that will resolve to `true` if the satellite modem enabled, + * `false` otherwise. + */ + private suspend fun requestIsEnabled( + context: Context, + ): Boolean = withContext(Default) { + val satelliteManager: SatelliteManager? = + context.getSystemService(SatelliteManager::class.java) + if (satelliteManager == null) { + Log.w(TAG, "SatelliteManager is null") + return@withContext false + } + + suspendCancellableCoroutine {continuation -> + satelliteManager?.requestIsEnabled(Default.asExecutor(), + object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> { + override fun onResult(result: Boolean) { + Log.i(TAG, "Satellite modem enabled status: $result") + continuation.resume(result) + } + + override fun onError(error: SatelliteManager.SatelliteException) { + super.onError(error) + Log.w(TAG, "Can't get satellite modem enabled status", error) + continuation.resume(false) + } + }) + } + } + + const val TAG = "SatelliteDialogUtils" + + const val EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG: String = + "extra_type_of_satellite_warning_dialog" + const val TYPE_IS_UNKNOWN = -1 + const val TYPE_IS_WIFI = 0 + const val TYPE_IS_BLUETOOTH = 1 + const val TYPE_IS_AIRPLANE_MODE = 2 +}
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt index 2a44511599f1..a939ed14b7c1 100644 --- a/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/statusbar/notification/data/repository/FakeNotificationsSoundPolicyRepository.kt @@ -17,6 +17,7 @@ package com.android.settingslib.statusbar.notification.data.repository import android.app.NotificationManager +import android.provider.Settings import com.android.settingslib.statusbar.notification.data.model.ZenMode import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -28,10 +29,14 @@ class FakeNotificationsSoundPolicyRepository : NotificationsSoundPolicyRepositor override val notificationPolicy: StateFlow<NotificationManager.Policy?> get() = mutableNotificationPolicy.asStateFlow() - private val mutableZenMode = MutableStateFlow<ZenMode?>(null) + private val mutableZenMode = MutableStateFlow<ZenMode?>(ZenMode(Settings.Global.ZEN_MODE_OFF)) override val zenMode: StateFlow<ZenMode?> get() = mutableZenMode.asStateFlow() + init { + updateNotificationPolicy() + } + fun updateNotificationPolicy(policy: NotificationManager.Policy?) { mutableNotificationPolicy.value = policy } @@ -48,13 +53,14 @@ fun FakeNotificationsSoundPolicyRepository.updateNotificationPolicy( suppressedVisualEffects: Int = NotificationManager.Policy.SUPPRESSED_EFFECTS_UNSET, state: Int = NotificationManager.Policy.STATE_UNSET, priorityConversationSenders: Int = NotificationManager.Policy.CONVERSATION_SENDERS_NONE, -) = updateNotificationPolicy( - NotificationManager.Policy( - priorityCategories, - priorityCallSenders, - priorityMessageSenders, - suppressedVisualEffects, - state, - priorityConversationSenders, +) = + updateNotificationPolicy( + NotificationManager.Policy( + priorityCategories, + priorityCallSenders, + priorityMessageSenders, + suppressedVisualEffects, + state, + priorityConversationSenders, + ) ) -)
\ No newline at end of file diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java index 23b2cc2df794..89f3cf5e9aab 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java +++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java @@ -278,7 +278,7 @@ public class MediaSessions { } @Override - public void onAudioInfoChanged(PlaybackInfo info) { + public void onAudioInfoChanged(@NonNull PlaybackInfo info) { if (D.BUG) { Log.d(TAG, cb("onAudioInfoChanged") + Util.playbackInfoToString(info) + " sentRemote=" + sentRemote); diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt index 65a5317ed0cb..36e396fb0c4f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -72,7 +72,11 @@ interface AudioRepository { suspend fun setVolume(audioStream: AudioStream, volume: Int) - suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) + /** + * Mutes and un-mutes [audioStream]. Returns true when the state changes and false the + * otherwise. + */ + suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) @@ -164,14 +168,20 @@ class AudioRepositoryImpl( audioManager.setStreamVolume(audioStream.value, volume, 0) } - override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) = - withContext(backgroundCoroutineContext) { - audioManager.adjustStreamVolume( - audioStream.value, - if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE, - 0, - ) + override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean { + return withContext(backgroundCoroutineContext) { + if (isMuted == audioManager.isStreamMute(audioStream.value)) { + false + } else { + audioManager.adjustStreamVolume( + audioStream.value, + if (isMuted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE, + 0, + ) + true + } } + } override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) { withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value } diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt index e4ac9fe686a3..195ccfcd328d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerRepository.kt @@ -21,6 +21,7 @@ import android.media.session.MediaSessionManager import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.bluetooth.headsetAudioModeChanges import com.android.settingslib.media.session.activeMediaChanges +import com.android.settingslib.media.session.defaultRemoteSessionChanged import com.android.settingslib.volume.shared.AudioManagerEventsReceiver import com.android.settingslib.volume.shared.model.AudioManagerEvent import kotlin.coroutines.CoroutineContext @@ -59,6 +60,9 @@ class MediaControllerRepositoryImpl( override val activeSessions: StateFlow<List<MediaController>> = merge( + mediaSessionManager.defaultRemoteSessionChanged.map { + mediaSessionManager.getActiveSessions(null) + }, mediaSessionManager.activeMediaChanges.filterNotNull(), localBluetoothManager?.headsetAudioModeChanges?.map { mediaSessionManager.getActiveSessions(null) diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt index 33f917e701c2..0e5ebdae96e4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt @@ -25,6 +25,7 @@ import com.android.settingslib.volume.shared.model.RingerMode import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map /** Provides audio stream state and an ability to change it */ @@ -46,8 +47,16 @@ class AudioVolumeInteractor( val ringerMode: StateFlow<RingerMode> get() = audioRepository.ringerMode - suspend fun setVolume(audioStream: AudioStream, volume: Int) = + suspend fun setVolume(audioStream: AudioStream, volume: Int) { + val streamModel = getAudioStream(audioStream).first() + val oldVolume = streamModel.volume audioRepository.setVolume(audioStream, volume) + when { + volume == streamModel.minVolume -> setMuted(audioStream, true) + oldVolume == streamModel.minVolume && volume > streamModel.minVolume -> + setMuted(audioStream, false) + } + } suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) { if (audioStream.value == AudioManager.STREAM_RING) { @@ -55,7 +64,16 @@ class AudioVolumeInteractor( if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL audioRepository.setRingerMode(audioStream, RingerMode(mode)) } - audioRepository.setMuted(audioStream, isMuted) + val mutedChanged = audioRepository.setMuted(audioStream, isMuted) + if (mutedChanged && !isMuted) { + with(getAudioStream(audioStream).first()) { + if (volume == minVolume) { + // Slightly increase volume when user un-mutes the stream that is lowered + // down to its minimum + setVolume(audioStream, volume + 1) + } + } + } } /** Checks if the volume can be changed via the UI. */ diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java index 3bd37a2c59bf..a2ee2ec9185e 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/media/InfoMediaManagerIntegTest.java @@ -65,7 +65,7 @@ public class InfoMediaManagerIntegTest { public void createInstance_withMR2FlagOn_returnsRouterInfoMediaManager() { InfoMediaManager manager = InfoMediaManager.createInstance( - mContext, mContext.getPackageName(), mContext.getUser(), null); + mContext, mContext.getPackageName(), mContext.getUser(), null, null); assertThat(manager).isInstanceOf(RouterInfoMediaManager.class); } @@ -73,14 +73,15 @@ public class InfoMediaManagerIntegTest { @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER) public void createInstance_withMR2FlagOn_withFakePackage_returnsNoOpInfoMediaManager() { InfoMediaManager manager = - InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null); + InfoMediaManager.createInstance(mContext, FAKE_PACKAGE, null, null, null); assertThat(manager).isInstanceOf(NoOpInfoMediaManager.class); } @Test @RequiresFlagsEnabled(FLAG_USE_MEDIA_ROUTER2_FOR_INFO_MEDIA_MANAGER) public void createInstance_withMR2FlagOn_withNullPackage_returnsRouterInfoMediaManager() { - InfoMediaManager manager = InfoMediaManager.createInstance(mContext, null, null, null); + InfoMediaManager manager = + InfoMediaManager.createInstance(mContext, null, null, null, null); assertThat(manager).isInstanceOf(RouterInfoMediaManager.class); } @@ -89,7 +90,7 @@ public class InfoMediaManagerIntegTest { public void createInstance_withMR2FlagOff_returnsManagerInfoMediaManager() { InfoMediaManager manager = InfoMediaManager.createInstance( - mContext, mContext.getPackageName(), mContext.getUser(), null); + mContext, mContext.getPackageName(), mContext.getUser(), null, null); assertThat(manager).isInstanceOf(ManagerInfoMediaManager.class); } } diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index f4ddd0ac59df..e125083488ed 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -41,7 +41,10 @@ android_app { //########################################################### android_robolectric_test { name: "SettingsLibRoboTests", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], static_libs: [ "Settings_robolectric_meta_service_file", "Robolectric_shadows_androidx_fragment_upstream", @@ -51,6 +54,7 @@ android_robolectric_test { "androidx.core_core", "flag-junit", "settingslib_media_flags_lib", + "settingslib_illustrationpreference_flags_lib", "testng", // TODO: remove once JUnit on Android provides assertThrows ], java_resource_dirs: ["config"], 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..a638df524740 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 @@ -15,6 +15,8 @@ */ package com.android.settingslib.bluetooth; +import static com.android.settingslib.flags.Flags.FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -28,16 +30,18 @@ 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; import android.net.Uri; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Pair; import com.android.settingslib.widget.AdaptiveIcon; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -80,13 +84,18 @@ 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"; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mSetFlagsRule.disableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA); when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager); when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast); when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant); @@ -252,6 +261,25 @@ public class BluetoothUtilsTest { } @Test + public void isAdvancedDetailsHeader_noMainIcon_returnFalse() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA); + + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON)).thenReturn(null); + + assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isFalse(); + } + + @Test + public void isAdvancedDetailsHeader_hasMainIcon_returnTrue() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA); + + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON)) + .thenReturn(STRING_METADATA.getBytes()); + + assertThat(BluetoothUtils.isAdvancedDetailsHeader(mBluetoothDevice)).isTrue(); + } + + @Test public void isAdvancedUntetheredDevice_untetheredHeadset_returnTrue() { when(mBluetoothDevice.getMetadata( BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn( @@ -293,6 +321,18 @@ public class BluetoothUtilsTest { } @Test + public void isAdvancedUntetheredDevice_untetheredHeadsetMetadataIsFalse_returnFalse() { + mSetFlagsRule.enableFlags(FLAG_ENABLE_DETERMINING_ADVANCED_DETAILS_HEADER_WITH_METADATA); + + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) + .thenReturn("false".getBytes()); + when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)) + .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes()); + + assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isFalse(); + } + + @Test public void isAvailableMediaBluetoothDevice_isConnectedLeAudioDevice_returnTrue() { when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true); when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); @@ -399,7 +439,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 +448,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 b356f542f480..0d814947527c 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,70 @@ 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%" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo("Left 10%"); + } + + @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%" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()).isEqualTo( + "Left 10%"); } @Test @@ -851,35 +888,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% Right 10%" result with Battery Level 10. + assertThat(mCachedDevice.getTvConnectionSummary().toString()) + .isEqualTo("Left 10% Right 10%"); } @Test @@ -1151,7 +1198,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 +1207,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 +1216,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( @@ -1214,11 +1261,7 @@ public class CachedBluetoothDeviceTest { TWS_BATTERY_RIGHT.getBytes()); assertThat(mCachedDevice.getTvConnectionSummary().toString()) - .isEqualTo( - mContext.getString(R.string.bluetooth_battery_level_untethered_left, "15%") - + " " - + mContext.getString( - R.string.bluetooth_battery_level_untethered_right, "25%")); + .isEqualTo("Left 15% Right 25%"); } @Test @@ -1235,10 +1278,8 @@ public class CachedBluetoothDeviceTest { .thenReturn(TWS_BATTERY_RIGHT.getBytes()); int lowBatteryColor = mContext.getColor(LOW_BATTERY_COLOR); - String leftBattery = - mContext.getString(R.string.bluetooth_battery_level_untethered_left, "15%"); - String rightBattery = - mContext.getString(R.string.bluetooth_battery_level_untethered_right, "25%"); + String leftBattery = "Left 15%"; + String rightBattery = "Right 25%"; // Default low battery threshold, only left battery is low CharSequence summary = mCachedDevice.getTvConnectionSummary(LOW_BATTERY_COLOR); @@ -1703,6 +1744,30 @@ public class CachedBluetoothDeviceTest { } @Test + public void setName_memberDeviceNameIsSet() { + when(mDevice.getAlias()).thenReturn(DEVICE_NAME); + when(mSubDevice.getAlias()).thenReturn(DEVICE_NAME); + + mCachedDevice.addMemberDevice(mSubCachedDevice); + mCachedDevice.setName(DEVICE_ALIAS); + + verify(mDevice).setAlias(DEVICE_ALIAS); + verify(mSubDevice).setAlias(DEVICE_ALIAS); + } + + @Test + public void setName_subDeviceNameIsSet() { + when(mDevice.getAlias()).thenReturn(DEVICE_NAME); + when(mSubDevice.getAlias()).thenReturn(DEVICE_NAME); + + mCachedDevice.setSubDevice(mSubCachedDevice); + mCachedDevice.setName(DEVICE_ALIAS); + + verify(mDevice).setAlias(DEVICE_ALIAS); + verify(mSubDevice).setAlias(DEVICE_ALIAS); + } + + @Test public void getProfileConnectionState_nullProfile_returnDisconnected() { assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo( BluetoothProfile.STATE_DISCONNECTED); @@ -1717,16 +1782,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); @@ -2006,6 +2061,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/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java index aa5a2984e70c..4188d2ec7aaa 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java @@ -72,14 +72,18 @@ public class HearingAidDeviceManagerTest { @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule(); - private final static long HISYNCID1 = 10; - private final static long HISYNCID2 = 11; - private final static String DEVICE_NAME_1 = "TestName_1"; - private final static String DEVICE_NAME_2 = "TestName_2"; - private final static String DEVICE_ALIAS_1 = "TestAlias_1"; - private final static String DEVICE_ALIAS_2 = "TestAlias_2"; - private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11"; - private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22"; + private static final long HISYNCID1 = 10; + private static final long HISYNCID2 = 11; + private static final int GROUP_ID_1 = 20; + private static final int GROUP_ID_2 = 21; + private static final int PRESET_INDEX_1 = 1; + private static final int PRESET_INDEX_2 = 2; + private static final String DEVICE_NAME_1 = "TestName_1"; + private static final String DEVICE_NAME_2 = "TestName_2"; + private static final String DEVICE_ALIAS_1 = "TestAlias_1"; + private static final String DEVICE_ALIAS_2 = "TestAlias_2"; + private static final String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11"; + private static final String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22"; private final BluetoothClass DEVICE_CLASS = createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE); private final Context mContext = ApplicationProvider.getApplicationContext(); @@ -295,6 +299,7 @@ public class HearingAidDeviceManagerTest { mHearingAidDeviceManager.setSubDeviceIfNeeded(mCachedDevice2); assertThat(mCachedDevice1.getSubDevice()).isEqualTo(mCachedDevice2); + verify(mDevice2).setAlias(DEVICE_ALIAS_1); } /** @@ -706,14 +711,73 @@ public class HearingAidDeviceManagerTest { } @Test - public void findMainDevice() { + public void findMainDevice_sameHiSyncId() { when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1); when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1); mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); mCachedDevice1.setSubDevice(mCachedDevice2); - assertThat(mHearingAidDeviceManager.findMainDevice(mCachedDevice2)). - isEqualTo(mCachedDevice1); + assertThat(mHearingAidDeviceManager.findMainDevice(mCachedDevice2)).isEqualTo( + mCachedDevice1); + } + + @Test + public void findMainDevice_sameGroupId() { + when(mCachedDevice1.getGroupId()).thenReturn(GROUP_ID_1); + when(mCachedDevice2.getGroupId()).thenReturn(GROUP_ID_2); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.addMemberDevice(mCachedDevice2); + + assertThat(mHearingAidDeviceManager.findMainDevice(mCachedDevice2)).isEqualTo( + mCachedDevice1); + } + + @Test + public void syncDeviceWithinSet_synchronized_differentPresetIndex_shouldNotSync() { + when(mHapClientProfile.getActivePresetIndex(mDevice1)).thenReturn(PRESET_INDEX_1); + when(mHapClientProfile.getActivePresetIndex(mDevice2)).thenReturn(PRESET_INDEX_2); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice1)).thenReturn(true); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice2)).thenReturn(true); + when(mCachedDevice1.getGroupId()).thenReturn(GROUP_ID_1); + when(mCachedDevice2.getGroupId()).thenReturn(GROUP_ID_2); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.addMemberDevice(mCachedDevice2); + + mHearingAidDeviceManager.syncDeviceIfNeeded(mCachedDevice1); + + verify(mHapClientProfile, never()).selectPreset(any(), anyInt()); + } + + @Test + public void syncDeviceWithinSet_unsynchronized_samePresetIndex_shouldNotSync() { + when(mHapClientProfile.getActivePresetIndex(mDevice1)).thenReturn(PRESET_INDEX_1); + when(mHapClientProfile.getActivePresetIndex(mDevice2)).thenReturn(PRESET_INDEX_1); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice1)).thenReturn(false); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice2)).thenReturn(false); + when(mCachedDevice1.getGroupId()).thenReturn(GROUP_ID_1); + when(mCachedDevice2.getGroupId()).thenReturn(GROUP_ID_2); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.addMemberDevice(mCachedDevice2); + + mHearingAidDeviceManager.syncDeviceIfNeeded(mCachedDevice1); + + verify(mHapClientProfile, never()).selectPreset(any(), anyInt()); + } + + @Test + public void syncDeviceWithinSet_unsynchronized_differentPresetIndex_shouldSync() { + when(mHapClientProfile.getActivePresetIndex(mDevice1)).thenReturn(PRESET_INDEX_1); + when(mHapClientProfile.getActivePresetIndex(mDevice2)).thenReturn(PRESET_INDEX_2); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice1)).thenReturn(false); + when(mHapClientProfile.supportsSynchronizedPresets(mDevice2)).thenReturn(false); + when(mCachedDevice1.getGroupId()).thenReturn(GROUP_ID_1); + when(mCachedDevice2.getGroupId()).thenReturn(GROUP_ID_2); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDevice1.addMemberDevice(mCachedDevice2); + + mHearingAidDeviceManager.syncDeviceIfNeeded(mCachedDevice2); + + verify(mHapClientProfile).selectPreset(mDevice2, PRESET_INDEX_1); } private HearingAidInfo getLeftAshaHearingAidInfo(long hiSyncId) { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java index bd5a022e44e5..cd1672104c5a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidStatsLogUtilsTest.java @@ -145,20 +145,29 @@ public class HearingAidStatsLogUtilsTest { } @Test - public void getUserCategory_hearingDevicesUser() { - prepareHearingDevicesUserHistory(); + public void getUserCategory_hearableDevicesUser() { + prepareHearableDevicesUserHistory(); assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo( - HearingAidStatsLogUtils.CATEGORY_HEARING_DEVICES); + HearingAidStatsLogUtils.CATEGORY_HEARABLE_DEVICES); } @Test - public void getUserCategory_newHearingDevicesUser() { - prepareHearingDevicesUserHistory(); + public void getUserCategory_newHearableDevicesUser() { + prepareHearableDevicesUserHistory(); prepareNewUserHistory(); assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo( - HearingAidStatsLogUtils.CATEGORY_NEW_HEARING_DEVICES); + HearingAidStatsLogUtils.CATEGORY_NEW_HEARABLE_DEVICES); + } + + @Test + public void getUserCategory_bothHearingAidsAndHearableDevicesUser_returnHearingAidsUser() { + prepareHearingAidsUserHistory(); + prepareHearableDevicesUserHistory(); + + assertThat(HearingAidStatsLogUtils.getUserCategory(mContext)).isEqualTo( + HearingAidStatsLogUtils.CATEGORY_HEARING_AIDS); } private long convertToStartOfDayTime(long timestamp) { @@ -176,12 +185,12 @@ public class HearingAidStatsLogUtilsTest { } } - private void prepareHearingDevicesUserHistory() { + private void prepareHearableDevicesUserHistory() { final long todayStartOfDay = convertToStartOfDayTime(System.currentTimeMillis()); for (int i = CONNECTED_HISTORY_EXPIRED_DAY - 1; i >= 0; i--) { final long data = todayStartOfDay - TimeUnit.DAYS.toMillis(i); HearingAidStatsLogUtils.addToHistory(mContext, - HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_CONNECTED, data); + HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_CONNECTED, data); } } @@ -191,6 +200,6 @@ public class HearingAidStatsLogUtilsTest { HearingAidStatsLogUtils.addToHistory(mContext, HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_AIDS_PAIRED, data); HearingAidStatsLogUtils.addToHistory(mContext, - HearingAidStatsLogUtils.HistoryType.TYPE_HEARING_DEVICES_PAIRED, data); + HearingAidStatsLogUtils.HistoryType.TYPE_HEARABLE_DEVICES_PAIRED, data); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java index cef083584744..6ff90ba4b391 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManagerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothPan; @@ -55,7 +56,9 @@ import java.util.List; @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowBluetoothAdapter.class}) public class LocalBluetoothProfileManagerTest { - private final static long HISYNCID = 10; + private static final long HISYNCID = 10; + + private static final int GROUP_ID = 1; @Mock private LocalBluetoothManager mBtManager; @Mock @@ -201,7 +204,8 @@ public class LocalBluetoothProfileManagerTest { * CachedBluetoothDeviceManager method */ @Test - public void stateChangedHandler_receiveHAPConnectionStateChanged_shouldDispatchDeviceManager() { + public void + stateChangedHandler_receiveHearingAidConnectionStateChanged_dispatchDeviceManager() { mShadowBluetoothAdapter.setSupportedProfiles(generateList( new int[] {BluetoothProfile.HEARING_AID})); mProfileManager.updateLocalProfiles(); @@ -219,6 +223,28 @@ public class LocalBluetoothProfileManagerTest { } /** + * Verify BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED with uuid intent will dispatch + * to {@link CachedBluetoothDeviceManager} method + */ + @Test + public void stateChangedHandler_receiveHapClientConnectionStateChanged_dispatchDeviceManager() { + mShadowBluetoothAdapter.setSupportedProfiles(generateList( + new int[] {BluetoothProfile.HAP_CLIENT})); + mProfileManager.updateLocalProfiles(); + when(mCachedBluetoothDevice.getGroupId()).thenReturn(GROUP_ID); + + mIntent = new Intent(BluetoothHapClient.ACTION_HAP_CONNECTION_STATE_CHANGED); + mIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + mIntent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, BluetoothProfile.STATE_CONNECTING); + mIntent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_CONNECTED); + + mContext.sendBroadcast(mIntent); + + verify(mDeviceManager).syncDeviceWithinHearingAidSetIfNeeded(mCachedBluetoothDevice, + BluetoothProfile.STATE_CONNECTED, BluetoothProfile.HAP_CLIENT); + } + + /** * Verify BluetoothPan.ACTION_CONNECTION_STATE_CHANGED intent with uuid will dispatch to * profile connection state changed callback */ diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index 69faddf48f19..ce07fe9fdf0a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -146,7 +146,11 @@ public class InfoMediaManagerTest { Context.MEDIA_SESSION_SERVICE); mInfoMediaManager = new ManagerInfoMediaManager( - mContext, TEST_PACKAGE_NAME, mContext.getUser(), mLocalBluetoothManager); + mContext, + TEST_PACKAGE_NAME, + mContext.getUser(), + mLocalBluetoothManager, + /* mediaController */ null); mShadowRouter2Manager = ShadowRouter2Manager.getShadow(); mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java index ddb5419509d7..12541bb51cc8 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java @@ -117,9 +117,16 @@ public class LocalMediaManagerTest { when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHapProfile); // Need to call constructor to initialize final fields. - mInfoMediaManager = mock( - InfoMediaManager.class, - withSettings().useConstructor(mContext, TEST_PACKAGE_NAME, mLocalBluetoothManager)); + mInfoMediaManager = + mock( + InfoMediaManager.class, + withSettings() + .useConstructor( + mContext, + TEST_PACKAGE_NAME, + android.os.Process.myUserHandle(), + mLocalBluetoothManager, + /* mediaController */ null)); doReturn( List.of( new RoutingSessionInfo.Builder(TEST_SESSION_ID, TEST_PACKAGE_NAME) diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java index 908f50deea78..c566741d19fc 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/NoOpInfoMediaManagerTest.java @@ -47,7 +47,8 @@ public class NoOpInfoMediaManagerTest { mContext, /* packageName */ "FAKE_PACKAGE_NAME", mContext.getUser(), - /* localBluetoothManager */ null); + /* localBluetoothManager */ null, + /* mediaController */ null); } @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt new file mode 100644 index 000000000000..aeda1ed66d16 --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/satellite/SatelliteDialogUtilsTest.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.satellite + +import android.content.Context +import android.content.Intent +import android.os.OutcomeReceiver +import android.platform.test.annotations.RequiresFlagsEnabled +import android.telephony.satellite.SatelliteManager +import android.telephony.satellite.SatelliteManager.SatelliteException +import android.util.AndroidRuntimeException +import androidx.test.core.app.ApplicationProvider +import com.android.internal.telephony.flags.Flags +import com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.any +import org.mockito.Mockito.`when` +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.verify +import org.mockito.internal.verification.Times +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class SatelliteDialogUtilsTest { + @JvmField + @Rule + val mockitoRule: MockitoRule = MockitoJUnit.rule() + + @Spy + var context: Context = ApplicationProvider.getApplicationContext() + @Mock + private lateinit var satelliteManager: SatelliteManager + + private val coroutineScope = CoroutineScope(Dispatchers.Main) + + @Before + fun setUp() { + `when`(context.getSystemService(SatelliteManager::class.java)) + .thenReturn(satelliteManager) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun mayStartSatelliteWarningDialog_satelliteIsOn_showWarningDialog() = runBlocking { + `when`( + satelliteManager.requestIsEnabled( + any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>() + ) + ) + .thenAnswer { invocation -> + val receiver = invocation + .getArgument< + OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>( + 1 + ) + receiver.onResult(true) + null + } + + try { + SatelliteDialogUtils.mayStartSatelliteWarningDialog( + context, coroutineScope, TYPE_IS_WIFI, allowClick = { + assertTrue(it) + }) + } catch (e: AndroidRuntimeException) { + // Catch exception of starting activity . + } + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun mayStartSatelliteWarningDialog_satelliteIsOff_notShowWarningDialog() = runBlocking { + `when`( + satelliteManager.requestIsEnabled( + any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>() + ) + ) + .thenAnswer { invocation -> + val receiver = invocation + .getArgument< + OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>( + 1 + ) + receiver.onResult(false) + null + } + + + SatelliteDialogUtils.mayStartSatelliteWarningDialog( + context, coroutineScope, TYPE_IS_WIFI, allowClick = { + assertFalse(it) + }) + + verify(context, Times(0)).startActivity(any<Intent>()) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun mayStartSatelliteWarningDialog_noSatelliteManager_notShowWarningDialog() = runBlocking { + `when`(context.getSystemService(SatelliteManager::class.java)) + .thenReturn(null) + + SatelliteDialogUtils.mayStartSatelliteWarningDialog( + context, coroutineScope, TYPE_IS_WIFI, allowClick = { + assertFalse(it) + }) + + verify(context, Times(0)).startActivity(any<Intent>()) + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun mayStartSatelliteWarningDialog_satelliteErrorResult_notShowWarningDialog() = runBlocking { + `when`( + satelliteManager.requestIsEnabled( + any(), any<OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>() + ) + ) + .thenAnswer { invocation -> + val receiver = invocation + .getArgument< + OutcomeReceiver<Boolean, SatelliteManager.SatelliteException>>( + 1 + ) + receiver.onError(SatelliteException(SatelliteManager.SATELLITE_RESULT_ERROR)) + null + } + + + SatelliteDialogUtils.mayStartSatelliteWarningDialog( + context, coroutineScope, TYPE_IS_WIFI, allowClick = { + assertFalse(it) + }) + + verify(context, Times(0)).startActivity(any<Intent>()) + } +} diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java index 6590bbdcdae6..ca53fc2ba47e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/IllustrationPreferenceTest.java @@ -26,10 +26,14 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; +import android.content.res.Resources; import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.AnimationDrawable; import android.net.Uri; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -39,11 +43,13 @@ import android.widget.ImageView; import androidx.preference.PreferenceViewHolder; import androidx.test.core.app.ApplicationProvider; +import com.android.settingslib.widget.flags.Flags; import com.android.settingslib.widget.preference.illustration.R; import com.airbnb.lottie.LottieAnimationView; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -51,10 +57,14 @@ import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; +import java.io.ByteArrayInputStream; + @RunWith(RobolectricTestRunner.class) public class IllustrationPreferenceTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Mock private ViewGroup mRootView; private Uri mImageUri; @@ -66,6 +76,7 @@ public class IllustrationPreferenceTest { private final Context mContext = ApplicationProvider.getApplicationContext(); private IllustrationPreference.OnBindListener mOnBindListener; private LottieAnimationView mOnBindListenerAnimationView; + private FrameLayout mIllustrationFrame; @Before public void setUp() { @@ -75,14 +86,14 @@ public class IllustrationPreferenceTest { mBackgroundView = new ImageView(mContext); mAnimationView = spy(new LottieAnimationView(mContext)); mMiddleGroundLayout = new FrameLayout(mContext); - final FrameLayout illustrationFrame = new FrameLayout(mContext); - illustrationFrame.setLayoutParams( + mIllustrationFrame = new FrameLayout(mContext); + mIllustrationFrame.setLayoutParams( new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); doReturn(mMiddleGroundLayout).when(mRootView).findViewById(R.id.middleground_layout); doReturn(mBackgroundView).when(mRootView).findViewById(R.id.background_view); doReturn(mAnimationView).when(mRootView).findViewById(R.id.lottie_view); - doReturn(illustrationFrame).when(mRootView).findViewById(R.id.illustration_frame); + doReturn(mIllustrationFrame).when(mRootView).findViewById(R.id.illustration_frame); mViewHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView)); final AttributeSet attributeSet = Robolectric.buildAttributeSet().build(); @@ -158,11 +169,13 @@ public class IllustrationPreferenceTest { } @Test + @DisableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES) public void playLottieAnimationWithResource_verifyFailureListener() { // fake the valid lottie image final int fakeValidResId = 111; doNothing().when(mAnimationView).setImageResource(fakeValidResId); doReturn(null).when(mAnimationView).getDrawable(); + doNothing().when(mAnimationView).setAnimation(fakeValidResId); mPreference.setLottieAnimationResId(fakeValidResId); mPreference.onBindViewHolder(mViewHolder); @@ -171,6 +184,50 @@ public class IllustrationPreferenceTest { } @Test + @DisableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES) + public void handleImageWithAnimation_emptyInputStreamDisabledFlag_verifyContainerVisible() { + doNothing().when(mAnimationView).setImageResource(111); + doReturn(null).when(mAnimationView).getDrawable(); + + mPreference.setLottieAnimationResId(111); + mPreference.onBindViewHolder(mViewHolder); + + assertThat(mAnimationView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test + @EnableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES) + public void handleImageWithAnimation_emptyInputStreamEnabledFlag_verifyContainerHidden() { + Resources res = spy(mContext.getResources()); + doReturn(res).when(mAnimationView).getResources(); + doReturn(new ByteArrayInputStream(new byte[] {})).when(res).openRawResource(111); + + mPreference.setLottieAnimationResId(111); + mPreference.onBindViewHolder(mViewHolder); + + assertThat(mAnimationView.getVisibility()).isEqualTo(View.GONE); + assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.GONE); + } + + @Test + @EnableFlags(Flags.FLAG_AUTO_HIDE_EMPTY_LOTTIE_RES) + public void handleImageWithAnimation_nonEmptyInputStreamEnabledFlag_verifyContainerVisible() { + Resources res = spy(mContext.getResources()); + doReturn(res).when(mAnimationView).getResources(); + doReturn(new ByteArrayInputStream(new byte[] { 1, 2, 3 })).when(res).openRawResource(111); + doNothing().when(mAnimationView).setImageResource(111); + doNothing().when(mAnimationView).setAnimation(111); + doReturn(null).when(mAnimationView).getDrawable(); + + mPreference.setLottieAnimationResId(111); + mPreference.onBindViewHolder(mViewHolder); + + assertThat(mAnimationView.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mIllustrationFrame.getVisibility()).isEqualTo(View.VISIBLE); + } + + @Test public void setMaxHeight_smallerThanRestrictedHeight_matchResult() { final int restrictedHeight = mContext.getResources().getDimensionPixelSize( diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java index 8f8445d7a40b..63e98de6826b 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java @@ -38,6 +38,8 @@ public class GlobalSettings { * NOTE: All settings which are backed up should have a corresponding validator. */ public static final String[] SETTINGS_TO_BACKUP = { + Settings.Global.CONNECTED_APPS_ALLOWED_PACKAGES, + Settings.Global.CONNECTED_APPS_DISALLOWED_PACKAGES, Settings.Global.APPLY_RAMPING_RINGER, Settings.Global.BUGREPORT_IN_POWER_MENU, // moved to secure Settings.Global.STAY_ON_WHILE_PLUGGED_IN, diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index be3f4108fdd1..888e39593a2e 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -274,6 +274,9 @@ public class SecureSettings { Settings.Secure.SCREEN_RESOLUTION_MODE, Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS, Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, - Settings.Secure.CHARGE_OPTIMIZATION_MODE + Settings.Secure.CHARGE_OPTIMIZATION_MODE, + Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, + Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, + Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, }; } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index c274534c8d36..bc0ee7fea6ef 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -51,6 +51,9 @@ public class GlobalSettingsValidators { public static final Map<String, Validator> VALIDATORS = new ArrayMap<>(); static { + VALIDATORS.put(Global.CONNECTED_APPS_ALLOWED_PACKAGES, new PackageNameListValidator((","))); + VALIDATORS.put(Global.CONNECTED_APPS_DISALLOWED_PACKAGES, + new PackageNameListValidator((","))); VALIDATORS.put(Global.APPLY_RAMPING_RINGER, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.BUGREPORT_IN_POWER_MENU, BOOLEAN_VALIDATOR); VALIDATORS.put( diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index b1feede57506..b992ddc8a397 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -18,6 +18,7 @@ package android.provider.settings.validators; import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.ANY_LONG_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.AUTOFILL_SERVICE_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; @@ -433,5 +434,8 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, new InclusiveIntegerRangeValidator(0, 10)); VALIDATORS.put(Secure.CHARGE_OPTIMIZATION_MODE, new InclusiveIntegerRangeValidator(0, 10)); + VALIDATORS.put(Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR); + VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, ANY_LONG_VALIDATOR); + VALIDATORS.put(Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, NONE_NEGATIVE_LONG_VALIDATOR); } } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java index 677c81ad9271..255b1ad3b3d2 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SettingsValidators.java @@ -239,6 +239,18 @@ public class SettingsValidators { } }; + static final Validator ANY_LONG_VALIDATOR = value -> { + if (value == null) { + return true; + } + try { + Long.parseLong(value); + return true; + } catch (NumberFormatException e) { + return false; + } + }; + static final Validator CREDENTIAL_SERVICE_VALIDATOR = new Validator() { @Override public boolean validate(String value) { 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..ab9a30b65e08 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -233,6 +233,7 @@ public class SettingsBackupTest { Settings.Global.ENHANCED_4G_MODE_ENABLED, Settings.Global.ENABLE_16K_PAGES, // Added for 16K developer option Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES, + Settings.Global.ERROR_KERNEL_LOG_PREFIX, Settings.Global.ERROR_LOGCAT_PREFIX, Settings.Global.EUICC_PROVISIONED, Settings.Global.EUICC_SUPPORTED_COUNTRIES, @@ -935,7 +936,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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 46bf494f2b1a..374240bb7262 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -347,6 +347,7 @@ <uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" /> <uses-permission android:name="android.permission.USE_COMPANION_TRANSPORTS" /> <uses-permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE" /> + <uses-permission android:name="android.permission.DELIVER_COMPANION_MESSAGES" /> <uses-permission android:name="android.permission.MANAGE_APPOPS" /> <uses-permission android:name="android.permission.WATCH_APPOPS" /> diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java index 4579168d2784..050a3704df1f 100644 --- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java +++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java @@ -200,7 +200,7 @@ public class BugreportReceiverTest { mBugreportFd = ParcelFileDescriptor.dup(invocation.getArgument(2)); return null; }).when(mMockIDumpstate).startBugreport(anyInt(), any(), any(), any(), anyInt(), anyInt(), - any(), anyBoolean()); + any(), anyBoolean(), anyBoolean()); setWarningState(mContext, STATE_HIDE); @@ -543,7 +543,7 @@ public class BugreportReceiverTest { getInstrumentation().waitForIdleSync(); verify(mMockIDumpstate, times(1)).startBugreport(anyInt(), any(), any(), any(), - anyInt(), anyInt(), any(), anyBoolean()); + anyInt(), anyInt(), any(), anyBoolean(), anyBoolean()); sendBugreportFinished(); } @@ -608,7 +608,7 @@ public class BugreportReceiverTest { ArgumentCaptor<IDumpstateListener> listenerCap = ArgumentCaptor.forClass( IDumpstateListener.class); verify(mMockIDumpstate, timeout(TIMEOUT)).startBugreport(anyInt(), any(), any(), any(), - anyInt(), anyInt(), listenerCap.capture(), anyBoolean()); + anyInt(), anyInt(), listenerCap.capture(), anyBoolean(), anyBoolean()); mIDumpstateListener = listenerCap.getValue(); assertNotNull("Dumpstate listener should not be null", mIDumpstateListener); mIDumpstateListener.onProgress(0); diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 65c570840368..ac680a97cf3a 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -78,11 +78,69 @@ filegroup { visibility: ["//visibility:private"], } +filegroup { + name: "SystemUI-tests-broken-robofiles-run", + srcs: [ + "tests/src/**/systemui/util/LifecycleFragmentTest.java", + "tests/src/**/systemui/util/TestableAlertDialogTest.kt", + "tests/src/**/systemui/util/kotlin/PairwiseFlowTest", + "tests/src/**/systemui/util/sensors/AsyncManagerTest.java", + "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java", + "tests/src/**/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java", + "tests/src/**/systemui/statusbar/KeyboardShortcutListSearchTest.java", + "tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java", + "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt", + "tests/src/**/systemui/statusbar/notification/AssistantFeedbackControllerTest.java", + "tests/src/**/systemui/statusbar/notification/collection/NotifCollectionTest.java", + "tests/src/**/systemui/statusbar/notification/collection/NotificationEntryTest.java", + "tests/src/**/systemui/statusbar/notification/collection/render/GroupExpansionManagerTest.kt", + "tests/src/**/systemui/statusbar/notification/collection/ShadeListBuilderTest.java", + "tests/src/**/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java", + "tests/src/**/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java", + "tests/src/**/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt", + "tests/src/**/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt", + "tests/src/**/systemui/statusbar/NotificationLockscreenUserManagerTest.java", + "tests/src/**/systemui/statusbar/notification/logging/NotificationLoggerTest.java", + "tests/src/**/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java", + "tests/src/**/systemui/statusbar/notification/row/NotificationContentInflaterTest.java", + "tests/src/**/systemui/statusbar/notification/row/NotificationContentViewTest.kt", + "tests/src/**/systemui/statusbar/notification/row/NotificationConversationInfoTest.java", + "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerTest.java", + "tests/src/**/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt", + "tests/src/**/systemui/statusbar/notification/row/NotifLayoutInflaterFactoryTest.kt", + "tests/src/**/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt", + "tests/src/**/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java", + "tests/src/**/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java", + "tests/src/**/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt", + "tests/src/**/systemui/statusbar/phone/AutoTileManagerTest.java", + "tests/src/**/systemui/statusbar/phone/CentralSurfacesImplTest.java", + "tests/src/**/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java", + "tests/src/**/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt", + "tests/src/**/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt", + "tests/src/**/systemui/statusbar/phone/PhoneStatusBarView.java", + "tests/src/**/systemui/statusbar/phone/PhoneStatusBarViewTest.kt", + "tests/src/**/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt", + "tests/src/**/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt", + "tests/src/**/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt", + "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt", + "tests/src/**/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt", + "tests/src/**/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt", + "tests/src/**/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt", + "tests/src/**/systemui/statusbar/policy/CallbackControllerTest.java", + "tests/src/**/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java", + "tests/src/**/systemui/statusbar/policy/InflatedSmartRepliesTest.java", + "tests/src/**/systemui/statusbar/policy/LocationControllerImplTest.java", + "tests/src/**/systemui/statusbar/policy/RemoteInputViewTest.java", + "tests/src/**/systemui/statusbar/policy/SmartReplyViewTest.java", + "tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt", + ], +} + // We are running robolectric tests in the tests directory as well as // multivalent tests. If you add a test, and it doesn't run in robolectric, // it should be added to this exclusion list. go/multivalent-tests filegroup { - name: "SystemUI-tests-broken-robofiles", + name: "SystemUI-tests-broken-robofiles-compile", srcs: [ "tests/src/**/*DeviceOnlyTest.java", "tests/src/**/*DeviceOnlyTest.kt", @@ -135,6 +193,9 @@ filegroup { "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt", + // TODO(b/322324387): Fails to start due to missing ScreenshotActivity + "tests/src/**/systemui/bouncer/ui/composable/BouncerContentTest.kt", + "tests/src/**/systemui/bouncer/ui/composable/PatternBouncerTest.kt", "tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt", "tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt", "tests/src/**/systemui/clipboardoverlay/ClipboardModelTest.kt", @@ -382,6 +443,7 @@ android_library { "androidx.compose.material_material-icons-extended", "androidx.activity_activity-compose", "androidx.compose.animation_animation-graphics", + "device_policy_aconfig_flags_lib", ], libs: [ "keepanno-annotations", @@ -474,6 +536,8 @@ android_library { "SystemUI-res", "WifiTrackerLib", "PlatformAnimationLib", + "PlatformMotionTestingCompose", + "ScreenshotComposeUtilsLib", "SystemUIPluginLib", "SystemUISharedLib", "SystemUICustomizationLib", @@ -568,7 +632,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", @@ -622,6 +686,7 @@ android_app { "//frameworks/libs/systemui:compilelib", "SystemUI-tests-base", "androidx.compose.runtime_runtime", + "SystemUI-core", ], libs: [ "keepanno-annotations", @@ -654,6 +719,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", @@ -700,10 +766,12 @@ android_robolectric_test { ":SystemUI-tests-robofiles", ], exclude_srcs: [ - ":SystemUI-tests-broken-robofiles", + ":SystemUI-tests-broken-robofiles-compile", + ":SystemUI-tests-broken-robofiles-run", ], static_libs: [ "RoboTestLibraries", + "mockito-kotlin2", ], libs: [ "android.test.runner", @@ -738,6 +806,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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 59e2b9175711..9c58371a387d 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -826,20 +826,6 @@ </intent-filter> </activity> - <activity - android:name=".contrast.ContrastDialogActivity" - android:label="@string/quick_settings_contrast_label" - android:theme="@style/Theme.SystemUI.ContrastDialog" - android:finishOnCloseSystemDialogs="true" - android:launchMode="singleInstance" - android:excludeFromRecents="true" - android:exported="true"> - <intent-filter> - <action android:name="com.android.intent.action.SHOW_CONTRAST_DIALOG" /> - <category android:name="android.intent.category.DEFAULT" /> - </intent-filter> - </activity> - <activity android:name=".ForegroundServicesDialog" android:process=":fgservices" android:excludeFromRecents="true" @@ -1110,7 +1096,7 @@ android:resource="@xml/home_controls_dream_metadata" /> </service> - <activity android:name="com.android.systemui.keyboard.shortcut.ShortcutHelperActivity" + <activity android:name="com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity" android:exported="false" android:theme="@style/ShortcutHelperTheme" android:excludeFromRecents="true" diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS index 796e3914f3c1..d2e5a13adfce 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -4,13 +4,13 @@ set noparent dsandler@android.com -aaronjli@google.com achalke@google.com acul@google.com adamcohen@google.com aioana@google.com alexflo@google.com andonian@google.com +amiko@google.com aroederer@google.com arteiro@google.com asc@google.com @@ -39,7 +39,6 @@ hwwang@google.com hyunyoungs@google.com ikateryna@google.com iyz@google.com -jamesoleary@google.com jbolinger@google.com jdemeulenaere@google.com jeffdq@google.com @@ -82,6 +81,7 @@ pixel@google.com pomini@google.com princedonkor@google.com rahulbanerjee@google.com +rgl@google.com roosa@google.com saff@google.com santie@google.com @@ -110,6 +110,3 @@ yeinj@google.com yuandizhou@google.com yurilin@google.com zakcohen@google.com - -#Android TV -rgl@google.com diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index 0c89a5dcbcf4..deab81856afa 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -59,13 +59,16 @@ ] } ], - + "auto-end-to-end-postsubmit": [ { "name": "AndroidAutomotiveHomeTests", "options" : [ { "include-filter": "android.platform.tests.HomeTest" + }, + { + "exclude-filter": "android.platform.tests.HomeTest#testAssistantWidget" } ] }, diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig index d868d5c7c4c4..ba842877fc79 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig +++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig @@ -9,3 +9,13 @@ flag { description: "Hides the AccessibilityMenuService UI before taking action instead of after." bug: "292020123" } + +flag { + name: "a11y_menu_snackbar_live_region" + namespace: "accessibility" + description: "configures live region on snackbar so its contents are announced when it appears." + bug: "338351484" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml index 15eb928c1ced..be1916fe4f4e 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-cs/strings.xml @@ -2,7 +2,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="accessibility_menu_service_name" msgid="730136711554740131">"Nabídka usnadnění přístupu"</string> - <string name="accessibility_menu_intro" msgid="3164193281544042394">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení. Můžete zamknout zařízení, upravit hlasitost a jas, pořídit snímek obrazovky apod."</string> + <string name="accessibility_menu_intro" msgid="3164193281544042394">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení – například k jeho zamknutí, úpravě hlasitosti a jasu, pořízení snímku obrazovky apod."</string> <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string> <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string> <string name="a11y_settings_label" msgid="3977714687248445050">"Nastavení usnadnění přístupu"</string> @@ -20,7 +20,7 @@ <string name="brightness_down_label" msgid="7115662941913272072">"Snížit jas"</string> <string name="previous_button_content_description" msgid="840869171117765966">"Zpět na předchozí obrazovku"</string> <string name="next_button_content_description" msgid="6810058269847364406">"Přejít na další obrazovku"</string> - <string name="accessibility_menu_description" msgid="4458354794093858297">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení. Můžete zamknout zařízení, upravit hlasitost a jas, pořídit snímek obrazovky apod."</string> + <string name="accessibility_menu_description" msgid="4458354794093858297">"Nabídka usnadnění přístupu zobrazuje na obrazovce velkou nabídku k ovládání zařízení – například k jeho zamknutí, úpravě hlasitosti a jasu, pořízení snímku obrazovky apod."</string> <string name="accessibility_menu_summary" msgid="340071398148208130">"Ovládání zařízení pomocí velké nabídky"</string> <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Nastavení nabídky usnadnění přístupu"</string> <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Velká tlačítka"</string> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-es/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-es/strings.xml index 877a43cc26cf..b97df645dee7 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-es/strings.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-es/strings.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menú Accesibilidad"</string> + <string name="accessibility_menu_service_name" msgid="730136711554740131">"Menú de accesibilidad"</string> <string name="accessibility_menu_intro" msgid="3164193281544042394">"El menú de accesibilidad es un menú de gran tamaño que se muestra en pantalla para controlar tu dispositivo. Puedes bloquear el dispositivo, controlar el volumen y el brillo, hacer capturas de pantalla y más."</string> <string name="assistant_label" msgid="6796392082252272356">"Asistente"</string> <string name="assistant_utterance" msgid="65509599221141377">"Asistente"</string> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml index b314c8eaea1d..1e21c775ffd9 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-eu/strings.xml @@ -20,7 +20,7 @@ <string name="brightness_down_label" msgid="7115662941913272072">"Txikitu distira"</string> <string name="previous_button_content_description" msgid="840869171117765966">"Joan aurreko pantailara"</string> <string name="next_button_content_description" msgid="6810058269847364406">"Joan hurrengo pantailara"</string> - <string name="accessibility_menu_description" msgid="4458354794093858297">"Erabilerraztasun-menuari esker, tamaina handiko menu bat izango duzu pantailan; menu horren bidez, gailua kontrolatzeko aukera izango duzu. Besteak beste, hauek egin ahalko dituzu: gailua blokeatu; bolumena eta distira kontrolatu, eta pantaila-argazkiak egin."</string> + <string name="accessibility_menu_description" msgid="4458354794093858297">"Erabilerraztasun-menuari esker, tamaina handiko menu bat izango duzu pantailan; menu horren bidez, gailua kontrolatzeko aukera izango duzu. Besteak beste, hauek egin ahalko dituzu: gailua blokeatu, bolumena eta distira kontrolatu, eta pantaila-argazkiak egin."</string> <string name="accessibility_menu_summary" msgid="340071398148208130">"Kontrolatu gailua menu handiaren bidez"</string> <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Erabilerraztasun-menuaren ezarpenak"</string> <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Botoi handiak"</string> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml index eff6cb45309b..5c3c99c59abc 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-hr/strings.xml @@ -2,7 +2,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="accessibility_menu_service_name" msgid="730136711554740131">"Izbornik pristupačnosti"</string> - <string name="accessibility_menu_intro" msgid="3164193281544042394">"Izbornik pristupačnosti veliki je zaslonski izbornik koji vam omogućuje upravljanje uređajem. Putem ovog izbornika možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string> + <string name="accessibility_menu_intro" msgid="3164193281544042394">"Izbornik pristupačnosti veliki je zaslonski izbornik koji vam omogućuje upravljanje uređajem. Putem njega možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string> <string name="assistant_label" msgid="6796392082252272356">"Asistent"</string> <string name="assistant_utterance" msgid="65509599221141377">"Asistent"</string> <string name="a11y_settings_label" msgid="3977714687248445050">"Postavke pristupačnosti"</string> @@ -20,7 +20,7 @@ <string name="brightness_down_label" msgid="7115662941913272072">"Smanji svjetlinu"</string> <string name="previous_button_content_description" msgid="840869171117765966">"Idi na prethodni zaslon"</string> <string name="next_button_content_description" msgid="6810058269847364406">"Idi na sljedeći zaslon"</string> - <string name="accessibility_menu_description" msgid="4458354794093858297">"Izbornik pristupačnosti pruža velik izbornik na zaslonu u svrhu upravljanja uređajem. Možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string> + <string name="accessibility_menu_description" msgid="4458354794093858297">"Izbornik pristupačnosti veliki je zaslonski izbornik koji vam omogućuje upravljanje uređajem. Putem njega možete zaključati uređaj, upravljati glasnoćom i svjetlinom, izrađivati snimke zaslona i drugo."</string> <string name="accessibility_menu_summary" msgid="340071398148208130">"Upravljanje uređajem pomoću velikog izbornika"</string> <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Postavke izbornika pristupačnosti"</string> <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Veliki gumbi"</string> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml index 38cc39505e86..d23ddcade19f 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values-tr/strings.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="accessibility_menu_service_name" msgid="730136711554740131">"Erişilebilirlik menüsü"</string> - <string name="accessibility_menu_intro" msgid="3164193281544042394">"Erişilebilirlik menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string> + <string name="accessibility_menu_service_name" msgid="730136711554740131">"Erişilebilirlik Menüsü"</string> + <string name="accessibility_menu_intro" msgid="3164193281544042394">"Erişilebilirlik Menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string> <string name="assistant_label" msgid="6796392082252272356">"Asistan"</string> <string name="assistant_utterance" msgid="65509599221141377">"Asistan"</string> <string name="a11y_settings_label" msgid="3977714687248445050">"Erişebilirlik Ayarları"</string> @@ -20,7 +20,7 @@ <string name="brightness_down_label" msgid="7115662941913272072">"Parlaklığı azalt"</string> <string name="previous_button_content_description" msgid="840869171117765966">"Önceki ekrana git"</string> <string name="next_button_content_description" msgid="6810058269847364406">"Sonraki ekrana git"</string> - <string name="accessibility_menu_description" msgid="4458354794093858297">"Erişilebilirlik menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string> + <string name="accessibility_menu_description" msgid="4458354794093858297">"Erişilebilirlik Menüsü, cihazınızı kontrol etmeniz için geniş bir ekran menüsü sağlar. Cihazınızı kilitleyebilir, ses düzeyini ve parlaklığı kontrol edebilir, ekran görüntüsü alabilir ve daha fazlasını yapabilirsiniz."</string> <string name="accessibility_menu_summary" msgid="340071398148208130">"Cihazı geniş menüyle kontrol edin"</string> <string name="accessibility_menu_settings_name" msgid="1716888058785672611">"Erişilebilirlik Menüsü Ayarları"</string> <string name="accessibility_menu_large_buttons_title" msgid="8978499601044961736">"Büyük düğmeler"</string> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java index 1be04f854c9a..7b43b72ee757 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility.accessibilitymenu.view; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY; import static java.lang.Math.max; @@ -321,7 +322,14 @@ public class A11yMenuOverlayLayout { AccessibilityManager.FLAG_CONTENT_TEXT); final TextView snackbar = mLayout.findViewById(R.id.snackbar); + if (snackbar == null) { + return; + } snackbar.setText(text); + if (com.android.systemui.accessibility.accessibilitymenu + .Flags.a11yMenuSnackbarLiveRegion()) { + snackbar.setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); + } // Remove any existing fade-out animation before starting any new animations. mHandler.removeCallbacksAndMessages(null); diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index 66943d453873..991ce12df2e5 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -30,7 +30,7 @@ import static com.android.systemui.accessibility.accessibilitymenu.Accessibility import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME; -import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.common.truth.Truth.assertThat; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Instrumentation; @@ -45,11 +45,11 @@ import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; import android.media.AudioManager; import android.os.PowerManager; -import android.os.RemoteException; import android.platform.uiautomator_helpers.WaitUtils; import android.provider.Settings; import android.util.Log; import android.view.Display; +import android.view.KeyEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -69,6 +69,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; +import java.io.IOException; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -88,6 +89,7 @@ public class AccessibilityMenuServiceTest { private static Instrumentation sInstrumentation; private static UiAutomation sUiAutomation; private static UiDevice sUiDevice; + private static String sLockSettings; private static final AtomicInteger sLastGlobalAction = new AtomicInteger(NO_GLOBAL_ACTION); private static final AtomicBoolean sOpenBlocked = new AtomicBoolean(false); @@ -106,12 +108,18 @@ public class AccessibilityMenuServiceTest { sUiAutomation.adoptShellPermissionIdentity( UiAutomation.ALL_PERMISSIONS.toArray(new String[0])); sUiDevice = UiDevice.getInstance(sInstrumentation); + sLockSettings = sUiDevice.executeShellCommand("locksettings get-disabled"); + Log.i(TAG, "locksettings get-disabled returns " + sLockSettings); + // Some test in the test class requires the device to be in lock screen + // ensure we have locksettings enabled before running the tests + sUiDevice.executeShellCommand("locksettings set-disabled false"); final Context context = sInstrumentation.getTargetContext(); sAccessibilityManager = context.getSystemService(AccessibilityManager.class); sPowerManager = context.getSystemService(PowerManager.class); sKeyguardManager = context.getSystemService(KeyguardManager.class); sDisplayManager = context.getSystemService(DisplayManager.class); + unlockSignal(); // Disable all a11yServices if any are active. if (!sAccessibilityManager.getEnabledAccessibilityServiceList( @@ -154,9 +162,10 @@ public class AccessibilityMenuServiceTest { } @AfterClass - public static void classTeardown() { + public static void classTeardown() throws IOException { Settings.Secure.putString(sInstrumentation.getTargetContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, ""); + sUiDevice.executeShellCommand("locksettings set-disabled " + sLockSettings); } @Before @@ -176,28 +185,32 @@ public class AccessibilityMenuServiceTest { } private static boolean isMenuVisible() { + sUiDevice.waitForIdle(); AccessibilityNodeInfo root = sUiAutomation.getRootInActiveWindow(); return root != null && root.getPackageName().toString().equals(PACKAGE_NAME); } - private static void wakeUpScreen() throws RemoteException { - sUiDevice.wakeUp(); + private static void wakeUpScreen() { + sUiDevice.pressKeyCode(KeyEvent.KEYCODE_WAKEUP); WaitUtils.waitForValueToSettle("Screen On", AccessibilityMenuServiceTest::isScreenOn); - assertWithMessage("Screen is on").that(isScreenOn()).isTrue(); + WaitUtils.ensureThat("Screen is on", AccessibilityMenuServiceTest::isScreenOn); } - private static void closeScreen() throws Throwable { - sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN); + private static void closeScreen() { + // go/adb-cheats#lock-screen + sUiDevice.pressKeyCode(KeyEvent.KEYCODE_SLEEP); WaitUtils.waitForValueToSettle("Screen Off", AccessibilityMenuServiceTest::isScreenOff); - assertWithMessage("Screen is off").that(isScreenOff()).isTrue(); + WaitUtils.ensureThat("Screen is off", AccessibilityMenuServiceTest::isScreenOff); + WaitUtils.ensureThat( + "Screen is locked", () -> sKeyguardManager.isKeyguardLocked()); } private static void openMenu() throws Throwable { unlockSignal(); if (!isMenuVisible()) { sInstrumentation.getTargetContext().sendBroadcast(INTENT_OPEN_MENU); - sUiDevice.waitForIdle(); - WaitUtils.ensureThat("Accessibility Menu is visible", () -> isMenuVisible()); + WaitUtils.ensureThat("Accessibility Menu is visible", + AccessibilityMenuServiceTest::isMenuVisible); } } @@ -449,6 +462,7 @@ public class AccessibilityMenuServiceTest { openMenu(); closeScreen(); wakeUpScreen(); + assertThat(sKeyguardManager.isKeyguardLocked()).isTrue(); TestUtils.waitUntil("Menu did not close.", TIMEOUT_UI_CHANGE_S, @@ -460,6 +474,8 @@ public class AccessibilityMenuServiceTest { public void testOnScreenLock_cannotOpenMenu() throws Throwable { closeScreen(); wakeUpScreen(); + assertThat(sKeyguardManager.isKeyguardLocked()).isTrue(); + sInstrumentation.getContext().sendBroadcast(INTENT_OPEN_MENU); sUiDevice.waitForIdle(); @@ -468,10 +484,7 @@ public class AccessibilityMenuServiceTest { sOpenBlocked::get); } - private static void unlockSignal() throws RemoteException { - if (!sKeyguardManager.isKeyguardLocked()) { - return; - } + private static void unlockSignal() throws IOException { // go/adb-cheats#unlock-screen wakeUpScreen(); if (sKeyguardManager.isKeyguardLocked()) { diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 14ebc3907c04..55edff6d9518 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -4,6 +4,23 @@ container: "system" # NOTE: Keep alphabetized to help limit merge conflicts from multiple simultaneous editors. flag { + name: "create_windowless_window_magnifier" + namespace: "accessibility" + description: "Uses SurfaceControlViewHost to create the magnifier for window magnification." + bug: "280992417" +} + +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." @@ -56,8 +73,11 @@ flag { } flag { - name: "create_windowless_window_magnifier" + name: "save_and_restore_magnification_settings_buttons" namespace: "accessibility" - description: "Uses SurfaceControlViewHost to create the magnifier for window magnification." - bug: "280992417" + description: "Saves the selected button status in magnification settings and restore the status when revisiting the same smallest screen DP." + bug: "325567876" + metadata { + purpose: PURPOSE_BUGFIX + } } diff --git a/packages/SystemUI/aconfig/cross_device_control.aconfig b/packages/SystemUI/aconfig/cross_device_control.aconfig deleted file mode 100644 index 5f9a4f42b546..000000000000 --- a/packages/SystemUI/aconfig/cross_device_control.aconfig +++ /dev/null @@ -1,16 +0,0 @@ -package: "com.android.systemui" -container: "system" - -flag { - name: "legacy_le_audio_sharing" - namespace: "pixel_cross_device_control" - description: "Gates the legacy le audio sharing UI." - bug: "322295262" -} - -flag { - name: "enable_personal_le_audio_sharing" - namespace: "pixel_cross_device_control" - description: "Gates the personal le audio sharing UI in UMO." - bug: "322295480" -} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 24d6413a0856..f3e2272792a8 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -26,11 +26,10 @@ flag { } flag { - - name: "notification_heads_up_cycling" - namespace: "systemui" - description: "Heads-up notification cycling animation for the Notification Avalanche feature." - bug: "316404716" + name: "priority_people_section" + namespace: "systemui" + description: "Add a new section for priority people (aka important conversations)." + bug: "340294566" } flag { @@ -171,16 +170,6 @@ flag { } flag { - name: "nssl_falsing_fix" - namespace: "systemui" - description: "Minor touch changes to prevent falsing errors in NSSL" - bug: "316551193" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "refactor_get_current_user" namespace: "systemui" description: "KeyguardUpdateMonitor.getCurrentUser() was providing outdated results." @@ -207,7 +196,16 @@ flag { description: "Re-enable the codepath that removed tinting of notifications when the" " standard background color is desired. This was the behavior before we discovered" " a resources threading issue, which we worked around by tinting the notification" - " backgrounds and footer buttons." + " backgrounds." + bug: "294830092" +} + +flag { + name: "notification_footer_background_tint_optimization" + namespace: "systemui" + description: "Remove duplicative tinting of notification footer buttons. This was the behavior" + " before we discovered a resources threading issue, which we worked around by applying the" + " same color as a tint to the background drawable of footer buttons." bug: "294830092" } @@ -358,6 +356,14 @@ flag { } flag { + name: "status_bar_screen_sharing_chips" + namespace: "systemui" + description: "Show chips on the left side of the status bar when a user is screen sharing, " + "recording, or casting" + bug: "332662551" +} + +flag { name: "compose_bouncer" namespace: "systemui" description: "Use the new compose bouncer in SystemUI" @@ -408,6 +414,23 @@ flag { } flag { + name: "confine_notification_touch_to_view_width" + namespace: "systemui" + description: "Use notification view width when detecting gestures." + bug: "335828150" +} + +flag { + name: "fix_image_wallpaper_crash_surface_already_released" + namespace: "systemui" + description: "Make sure ImageWallpaper doesn't return from OnSurfaceDestroyed until any drawing is finished" + bug: "337287154" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "activity_transition_use_largest_window" namespace: "systemui" description: "Target largest opening window during activity transitions." @@ -480,6 +503,26 @@ flag { } flag { + name: "fix_screenshot_action_dismiss_system_windows" + namespace: "systemui" + description: "Dismiss existing system windows when starting action from screenshot UI" + bug: "309933761" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "screenshot_scroll_crop_view_crash_fix" + namespace: "systemui" + description: "Mitigate crash on invalid computed range in CropView" + bug: "232633995" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "screenshot_private_profile_behavior_fix" namespace: "systemui" description: "Private profile support for screenshots" @@ -560,6 +603,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" @@ -581,6 +631,16 @@ flag { } flag { + name: "contextual_tips_assistant_dismiss_fix" + namespace: "systemui" + description: "Improve assistant dismiss signal accuracy for contextual tips." + bug: "334759504" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "shaderlib_loading_effect_refactor" namespace: "systemui" description: "Extend shader library to provide the common loading effects." @@ -796,7 +856,17 @@ flag { name: "dream_input_session_pilfer_once" namespace: "systemui" description: "Pilfer at most once per input session" - bug: "324600132" + bug: "333596426" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "shade_collapse_activity_launch_fix" + namespace: "systemui" + description: "Avoid collapsing the shade on activity launch if it is already collapsed, as this causes a flicker." + bug: "331591373" metadata { purpose: PURPOSE_BUGFIX } @@ -827,6 +897,9 @@ flag { namespace: "systemui" description: "Enforce BaseUserRestriction for DISALLOW_CONFIG_BRIGHTNESS." bug: "329205638" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { @@ -840,6 +913,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." @@ -848,3 +931,57 @@ 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 + } +} + +flag { + name: "validate_keyboard_shortcut_helper_icon_uri" + namespace: "systemui" + description: "Adds a check that the caller can access the content URI of an icon in the shortcut helper." + bug: "331180422" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "glanceable_hub_gesture_handle" + namespace: "systemui" + description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub" + bug: "339667383" +} + +flag { + name: "register_wallpaper_notifier_background" + namespace: "systemui" + description: "Decide whether to register wallpaper change broadcast receiver on background executor." + bug: "327315860" + 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/checks/Android.bp b/packages/SystemUI/checks/Android.bp index addcaf4f6319..04ac748d0c78 100644 --- a/packages/SystemUI/checks/Android.bp +++ b/packages/SystemUI/checks/Android.bp @@ -38,8 +38,9 @@ java_test_host { defaults: ["AndroidLintCheckerTestDefaults"], srcs: ["tests/**/*.kt"], data: [ - ":framework", ":androidx.annotation_annotation", + ":dagger2", + ":framework", ":kotlinx-coroutines-core", ], static_libs: [ diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt new file mode 100644 index 000000000000..94620c4c73b4 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/CollectAsStateDetector.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.client.api.UElementHandler +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UFile +import org.jetbrains.uast.UImportStatement + +class CollectAsStateDetector : Detector(), SourceCodeScanner { + + override fun getApplicableUastTypes(): List<Class<out UElement>> { + return listOf(UFile::class.java) + } + + override fun createUastHandler(context: JavaContext): UElementHandler { + return object : UElementHandler() { + override fun visitFile(node: UFile) { + node.imports.forEach { importStatement -> + visitImportStatement(context, importStatement) + } + } + } + } + + private fun visitImportStatement( + context: JavaContext, + importStatement: UImportStatement, + ) { + val importText = importStatement.importReference?.asSourceString() ?: return + if (ILLEGAL_IMPORT == importText) { + context.report( + issue = ISSUE, + scope = importStatement, + location = context.getLocation(importStatement), + message = "collectAsState considered harmful", + ) + } + } + + companion object { + @JvmField + val ISSUE = + Issue.create( + id = "OverlyEagerCollectAsState", + briefDescription = "collectAsState considered harmful", + explanation = + """ + go/sysui-compose#collect-as-state + + Don't use collectAsState as it will set up a coroutine that keeps collecting from a + flow until its coroutine scope becomes inactive. This prevents the work from being + properly paused while the surrounding lifecycle becomes paused or stopped and is + therefore considered harmful. + + Instead, use Flow.collectAsStateWithLifecycle(initial: T) or + StateFlow.collectAsStateWithLifecycle(). These APIs correctly pause the collection + coroutine while the lifecycle drops below the specified minActiveState (which + defaults to STARTED meaning that it will pause when the Compose-hosting window + becomes invisible). + """ + .trimIndent(), + category = Category.PERFORMANCE, + priority = 8, + severity = Severity.ERROR, + implementation = + Implementation( + CollectAsStateDetector::class.java, + Scope.JAVA_FILE_SCOPE, + ), + ) + + private val ILLEGAL_IMPORT = "androidx.compose.runtime.collectAsState" + } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt new file mode 100644 index 000000000000..68ec1ee8af97 --- /dev/null +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SingletonAndroidComponentDetector.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.detector.api.AnnotationInfo +import com.android.tools.lint.detector.api.AnnotationUsageInfo +import com.android.tools.lint.detector.api.AnnotationUsageType +import com.android.tools.lint.detector.api.Category +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Implementation +import com.android.tools.lint.detector.api.Issue +import com.android.tools.lint.detector.api.JavaContext +import com.android.tools.lint.detector.api.Scope +import com.android.tools.lint.detector.api.Severity +import com.android.tools.lint.detector.api.SourceCodeScanner +import org.jetbrains.uast.UAnnotation +import org.jetbrains.uast.UClass +import org.jetbrains.uast.UElement +import org.jetbrains.uast.UMethod + +/** + * Prevents binding Activities, Services, and BroadcastReceivers as Singletons in the Dagger graph. + * + * It is OK to mark a BroadcastReceiver as singleton as long as it is being constructed/injected and + * registered directly in the code. If instead it is declared in the manifest, and we let Android + * construct it for us, we also need to let Android destroy it for us, so don't allow marking it as + * singleton. + */ +class SingletonAndroidComponentDetector : Detector(), SourceCodeScanner { + override fun applicableAnnotations(): List<String> { + return listOf( + "com.android.systemui.dagger.SysUISingleton", + ) + } + + override fun isApplicableAnnotationUsage(type: AnnotationUsageType): Boolean = + type == AnnotationUsageType.DEFINITION + + override fun visitAnnotationUsage( + context: JavaContext, + element: UElement, + annotationInfo: AnnotationInfo, + usageInfo: AnnotationUsageInfo + ) { + if (element !is UAnnotation) { + return + } + + val parent = element.uastParent ?: return + + if (isInvalidBindingMethod(parent)) { + context.report( + ISSUE, + element, + context.getLocation(element), + "Do not bind Activities, Services, or BroadcastReceivers as Singleton." + ) + } else if (isInvalidClassDeclaration(parent)) { + context.report( + ISSUE, + element, + context.getLocation(element), + "Do not mark Activities or Services as Singleton." + ) + } + } + + private fun isInvalidBindingMethod(parent: UElement): Boolean { + if (parent !is UMethod) { + return false + } + + if ( + parent.returnType?.canonicalText !in + listOf( + "android.app.Activity", + "android.app.Service", + "android.content.BroadcastReceiver", + ) + ) { + return false + } + + if ( + !MULTIBIND_ANNOTATIONS.all { it in parent.annotations.map { it.qualifiedName } } && + !MULTIPROVIDE_ANNOTATIONS.all { it in parent.annotations.map { it.qualifiedName } } + ) { + return false + } + return true + } + + private fun isInvalidClassDeclaration(parent: UElement): Boolean { + if (parent !is UClass) { + return false + } + + if ( + parent.javaPsi.superClass?.qualifiedName !in + listOf( + "android.app.Activity", + "android.app.Service", + // Fine to mark BroadcastReceiver as singleton in this scenario + ) + ) { + return false + } + + return true + } + + companion object { + @JvmField + val ISSUE: Issue = + Issue.create( + id = "SingletonAndroidComponent", + briefDescription = "Activity, Service, or BroadcastReceiver marked as Singleton", + explanation = + """Activities, Services, and BroadcastReceivers are created and destroyed by + the Android System Server. Marking them with a Dagger scope + results in them being cached and reused by Dagger. Trying to reuse a + component like this will make for a very bad time.""", + category = Category.CORRECTNESS, + priority = 10, + severity = Severity.ERROR, + moreInfo = + "https://developer.android.com/guide/components/activities/process-lifecycle", + // Note that JAVA_FILE_SCOPE also includes Kotlin source files. + implementation = + Implementation( + SingletonAndroidComponentDetector::class.java, + Scope.JAVA_FILE_SCOPE + ) + ) + + private val MULTIBIND_ANNOTATIONS = + listOf("dagger.Binds", "dagger.multibindings.IntoMap", "dagger.multibindings.ClassKey") + + val MULTIPROVIDE_ANNOTATIONS = + listOf( + "dagger.Provides", + "dagger.multibindings.IntoMap", + "dagger.multibindings.ClassKey" + ) + } +} diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt index e93264c8e7b3..73ac6ccf8f76 100644 --- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt +++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/SystemUIIssueRegistry.kt @@ -32,6 +32,7 @@ class SystemUIIssueRegistry : IssueRegistry() { BindServiceOnMainThreadDetector.ISSUE, BroadcastSentViaContextDetector.ISSUE, CleanArchitectureDependencyViolationDetector.ISSUE, + CollectAsStateDetector.ISSUE, DumpableNotRegisteredDetector.ISSUE, FlowDetector.SHARED_FLOW_CREATION, SlowUserQueryDetector.ISSUE_SLOW_USER_ID_QUERY, @@ -40,6 +41,7 @@ class SystemUIIssueRegistry : IssueRegistry() { RegisterReceiverViaContextDetector.ISSUE, SoftwareBitmapDetector.ISSUE, NonInjectedServiceDetector.ISSUE, + SingletonAndroidComponentDetector.ISSUE, StaticSettingsProviderDetector.ISSUE, DemotingTestWithoutBugDetector.ISSUE, TestFunctionNameViolationDetector.ISSUE, diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt index e1cca88a8fe0..8396f3fee892 100644 --- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/AndroidStubs.kt @@ -21,8 +21,9 @@ import java.io.File internal val libraryNames = arrayOf( - "framework.jar", "androidx.annotation_annotation.jar", + "dagger2.jar", + "framework.jar", "kotlinx-coroutines-core.jar", ) diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt new file mode 100644 index 000000000000..6962b4eb31e6 --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/CollectAsStateDetectorTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +class CollectAsStateDetectorTest : SystemUILintDetectorTest() { + + override fun getDetector(): Detector { + return CollectAsStateDetector() + } + + override fun getIssues(): List<Issue> { + return listOf( + CollectAsStateDetector.ISSUE, + ) + } + + @Test + fun testViolation() { + lint() + .files(COLLECT_AS_STATE_STUB, COLLECT_WITH_LIFECYCLE_AS_STATE_STUB, GOOD_FILE, BAD_FILE) + .issues(CollectAsStateDetector.ISSUE) + .run() + .expect( + """ +src/com/android/internal/systemui/lint/Bad.kt:3: Error: collectAsState considered harmful [OverlyEagerCollectAsState] +import androidx.compose.runtime.collectAsState +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +1 errors, 0 warnings + """ + .trimIndent() + ) + } + + @Test + fun testNoViolation() { + lint() + .files(COLLECT_AS_STATE_STUB, COLLECT_WITH_LIFECYCLE_AS_STATE_STUB, GOOD_FILE) + .issues(CollectAsStateDetector.ISSUE) + .run() + .expectClean() + } + + companion object { + private val COLLECT_AS_STATE_STUB = + TestFiles.kotlin( + """ + package androidx.compose.runtime + + fun collectAsState() {} + """ + .trimIndent() + ) + private val COLLECT_WITH_LIFECYCLE_AS_STATE_STUB = + TestFiles.kotlin( + """ + package androidx.lifecycle.compose + + fun collectAsStateWithLifecycle() {} + """ + .trimIndent() + ) + + private val BAD_FILE = + TestFiles.kotlin( + """ + package com.android.internal.systemui.lint + + import androidx.compose.runtime.collectAsState + + class Bad + """ + .trimIndent() + ) + + private val GOOD_FILE = + TestFiles.kotlin( + """ + package com.android.internal.systemui.lint + + import androidx.lifecycle.compose.collectAsStateWithLifecycle + + class Good + """ + .trimIndent() + ) + } +} diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt new file mode 100644 index 000000000000..0606af80f27a --- /dev/null +++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/SingletonAndroidComponentDetectorTest.kt @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.systemui.lint + +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestFiles +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue +import org.junit.Test + +class SingletonAndroidComponentDetectorTest : SystemUILintDetectorTest() { + override fun getDetector(): Detector = SingletonAndroidComponentDetector() + + override fun getIssues(): List<Issue> = listOf(SingletonAndroidComponentDetector.ISSUE) + + @Test + fun testBindsServiceAsSingleton() { + lint() + .files( + TestFiles.kotlin( + """ + package test.pkg + + import android.app.Service + import com.android.systemui.dagger.SysUISingleton + import dagger.Binds + import dagger.Module + import dagger.multibindings.ClassKey + import dagger.multibindings.IntoMap + + @Module + interface BadModule { + @SysUISingleton + @Binds + @IntoMap + @ClassKey(SingletonService::class) + fun bindSingletonService(service: SingletonService): Service + } + """ + .trimIndent() + ), + *stubs + ) + .issues(SingletonAndroidComponentDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/BadModule.kt:12: Error: Do not bind Activities, Services, or BroadcastReceivers as Singleton. [SingletonAndroidComponent] + @SysUISingleton + ~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + .trimIndent() + ) + } + + @Test + fun testProvidesBroadcastReceiverAsSingleton() { + lint() + .files( + TestFiles.kotlin( + """ + package test.pkg + + import android.content.BroadcastReceiver + import com.android.systemui.dagger.SysUISingleton + import dagger.Provides + import dagger.Module + import dagger.multibindings.ClassKey + import dagger.multibindings.IntoMap + + @Module + abstract class BadModule { + @SysUISingleton + @Provides + @IntoMap + @ClassKey(SingletonBroadcastReceiver::class) + fun providesSingletonBroadcastReceiver(br: SingletonBroadcastReceiver): BroadcastReceiver { + return br + } + } + """ + .trimIndent() + ), + *stubs + ) + .issues(SingletonAndroidComponentDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/BadModule.kt:12: Error: Do not bind Activities, Services, or BroadcastReceivers as Singleton. [SingletonAndroidComponent] + @SysUISingleton + ~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + .trimIndent() + ) + } + @Test + fun testMarksActivityAsSingleton() { + lint() + .files( + TestFiles.kotlin( + """ + package test.pkg + + import android.app.Activity + import com.android.systemui.dagger.SysUISingleton + + @SysUISingleton + class BadActivity : Activity() { + } + """ + .trimIndent() + ), + *stubs + ) + .issues(SingletonAndroidComponentDetector.ISSUE) + .run() + .expect( + """ + src/test/pkg/BadActivity.kt:6: Error: Do not mark Activities or Services as Singleton. [SingletonAndroidComponent] + @SysUISingleton + ~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + .trimIndent() + ) + } + @Test + fun testMarksBroadcastReceiverAsSingleton() { + lint() + .files( + TestFiles.kotlin( + """ + package test.pkg + + import android.content.BroadcastReceiver + import com.android.systemui.dagger.SysUISingleton + + @SysUISingleton + class SingletonReceveiver : BroadcastReceiver() { + } + """ + .trimIndent() + ), + *stubs + ) + .issues(SingletonAndroidComponentDetector.ISSUE) + .run() + .expectClean() + } + + // Define stubs for Android imports. The tests don't run on Android so + // they don't "see" any of Android specific classes. We need to define + // the method parameters for proper resolution. + private val singletonStub: TestFile = + java( + """ + package com.android.systemui.dagger; + + public @interface SysUISingleton { + } + """ + ) + + private val stubs = arrayOf(singletonStub) + androidStubs +} diff --git a/packages/SystemUI/compose/core/Android.bp b/packages/SystemUI/compose/core/Android.bp index 49ae821dd2fb..c63c2b48638c 100644 --- a/packages/SystemUI/compose/core/Android.bp +++ b/packages/SystemUI/compose/core/Android.bp @@ -32,6 +32,7 @@ android_library { static_libs: [ "PlatformAnimationLib", + "PlatformMotionTestingComposeValues", "androidx.compose.runtime_runtime", "androidx.compose.material3_material3", 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/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.kt new file mode 100644 index 000000000000..9b736b8edcbf --- /dev/null +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/NotificationsShadeSceneModule.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.scene + +import com.android.systemui.notifications.ui.composable.NotificationsShadeScene +import com.android.systemui.scene.shared.model.Scene +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +interface NotificationsShadeSceneModule { + + @Binds @IntoSet fun notificationsShade(scene: NotificationsShadeScene): Scene +} diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.kt new file mode 100644 index 000000000000..3d7401d8f263 --- /dev/null +++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/QuickSettingsShadeSceneModule.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.scene + +import com.android.systemui.qs.ui.composable.QuickSettingsShadeScene +import com.android.systemui.scene.shared.model.Scene +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +interface QuickSettingsShadeSceneModule { + + @Binds @IntoSet fun quickSettingsShade(scene: QuickSettingsShadeScene): Scene +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index c22b50d9dd64..c19c08e09349 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -20,6 +20,7 @@ package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog import android.content.DialogInterface +import androidx.annotation.VisibleForTesting import androidx.compose.animation.Crossfade import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.animateFloatAsState @@ -56,7 +57,6 @@ import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -70,6 +70,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset @@ -77,6 +78,7 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.times +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformButton import com.android.compose.animation.Easings import com.android.compose.animation.scene.ElementKey @@ -104,6 +106,9 @@ import com.android.systemui.res.R import kotlin.math.abs import kotlin.math.max import kotlin.math.pow +import platform.test.motion.compose.values.MotionTestValueKey +import platform.test.motion.compose.values.MotionTestValues +import platform.test.motion.compose.values.motionTestValues @Composable fun BouncerContent( @@ -111,9 +116,20 @@ fun BouncerContent( dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { - val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() + val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsStateWithLifecycle() val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) + BouncerContent(layout, viewModel, dialogFactory, modifier) +} + +@Composable +@VisibleForTesting +fun BouncerContent( + layout: BouncerSceneLayout, + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, + modifier: Modifier +) { Box( // Allows the content within each of the layouts to react to the appearance and // disappearance of the IME, which is also known as the software keyboard. @@ -220,7 +236,7 @@ private fun SplitLayout( viewModel: BouncerViewModel, modifier: Modifier = Modifier, ) { - val authMethod by viewModel.authMethodViewModel.collectAsState() + val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle() Row( modifier = @@ -316,7 +332,9 @@ private fun BesideUserSwitcherLayout( val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } val isHeightExpanded = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded - val authMethod by viewModel.authMethodViewModel.collectAsState() + val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle() + + var swapAnimationEnd by remember { mutableStateOf(false) } Row( modifier = @@ -331,11 +349,16 @@ private fun BesideUserSwitcherLayout( } ) } + .testTag("BesideUserSwitcherLayout") + .motionTestValues { + swapAnimationEnd exportAs BouncerMotionTestKeys.swapAnimationEnd + } .padding( top = if (isHeightExpanded) 128.dp else 96.dp, bottom = if (isHeightExpanded) 128.dp else 48.dp, ), ) { + LaunchedEffect(isSwapped) { swapAnimationEnd = false } val animatedOffset by animateFloatAsState( targetValue = @@ -354,31 +377,35 @@ private fun BesideUserSwitcherLayout( -1f }, label = "offset", - ) + ) { + swapAnimationEnd = true + } fun Modifier.swappable(inversed: Boolean = false): Modifier { return graphicsLayer { - translationX = - size.width * - animatedOffset * - if (inversed) { - // A negative sign is used to make sure this is offset in the direction - // that's opposite to the direction that the user switcher is pushed in. - -1 - } else { - 1 - } - alpha = animatedAlpha(animatedOffset) - } + translationX = + size.width * + animatedOffset * + if (inversed) { + // A negative sign is used to make sure this is offset in the + // direction that's opposite to the direction that the user + // switcher is pushed in. + -1 + } else { + 1 + } + alpha = animatedAlpha(animatedOffset) + } + .motionTestValues { animatedAlpha(animatedOffset) exportAs MotionTestValues.alpha } } UserSwitcher( viewModel = viewModel, - modifier = Modifier.weight(1f).swappable(), + modifier = Modifier.weight(1f).swappable().testTag("UserSwitcher"), ) FoldAware( - modifier = Modifier.weight(1f).swappable(inversed = true), + modifier = Modifier.weight(1f).swappable(inversed = true).testTag("FoldAware"), viewModel = viewModel, aboveFold = { Column( @@ -389,7 +416,10 @@ private fun BesideUserSwitcherLayout( viewModel = viewModel.message, ) - OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + OutputArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 24.dp).testTag("OutputArea") + ) } }, belowFold = { @@ -412,13 +442,13 @@ private fun BesideUserSwitcherLayout( viewModel = viewModel, pinButtonRowVerticalSpacing = 12.dp, centerPatternDotsVertically = true, - modifier = Modifier.align(Alignment.BottomCenter), + modifier = Modifier.align(Alignment.BottomCenter).testTag("InputArea"), ) } ActionArea( viewModel = viewModel, - modifier = Modifier.padding(top = 48.dp), + modifier = Modifier.padding(top = 48.dp).testTag("ActionArea"), ) } }, @@ -480,7 +510,7 @@ private fun FoldAware( modifier: Modifier = Modifier, ) { val foldPosture: FoldPosture by foldPosture() - val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() + val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsStateWithLifecycle() val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired val currentSceneKey = if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey @@ -562,7 +592,7 @@ private fun StatusMessage( viewModel: BouncerMessageViewModel, modifier: Modifier = Modifier, ) { - val message: MessageViewModel? by viewModel.message.collectAsState() + val message: MessageViewModel? by viewModel.message.collectAsStateWithLifecycle() DisposableEffect(Unit) { viewModel.onShown() @@ -612,7 +642,7 @@ private fun OutputArea( modifier: Modifier = Modifier, ) { val authMethodViewModel: AuthMethodBouncerViewModel? by - viewModel.authMethodViewModel.collectAsState() + viewModel.authMethodViewModel.collectAsStateWithLifecycle() when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> @@ -642,7 +672,7 @@ private fun InputArea( modifier: Modifier = Modifier, ) { val authMethodViewModel: AuthMethodBouncerViewModel? by - viewModel.authMethodViewModel.collectAsState() + viewModel.authMethodViewModel.collectAsStateWithLifecycle() when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> { @@ -668,7 +698,8 @@ private fun ActionArea( viewModel: BouncerViewModel, modifier: Modifier = Modifier, ) { - val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + val actionButton: BouncerActionButtonModel? by + viewModel.actionButton.collectAsStateWithLifecycle() val appearFadeInAnimatable = remember { Animatable(0f) } val appearMoveAnimatable = remember { Animatable(0f) } val appearAnimationInitialOffset = with(LocalDensity.current) { 80.dp.toPx() } @@ -735,7 +766,7 @@ private fun Dialog( bouncerViewModel: BouncerViewModel, dialogFactory: BouncerDialogFactory, ) { - val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsState() + val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsStateWithLifecycle() var dialog: AlertDialog? by remember { mutableStateOf(null) } dialogViewModel?.let { viewModel -> @@ -772,8 +803,8 @@ private fun UserSwitcher( return } - val selectedUserImage by viewModel.selectedUserImage.collectAsState(null) - val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList()) + val selectedUserImage by viewModel.selectedUserImage.collectAsStateWithLifecycle(null) + val dropdownItems by viewModel.userSwitcherDropdown.collectAsStateWithLifecycle(emptyList()) Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -937,3 +968,8 @@ private object SceneElements { private val SceneTransitions = transitions { from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() } } + +@VisibleForTesting +object BouncerMotionTestKeys { + val swapAnimationEnd = MotionTestValueKey<Boolean>("swapAnimationEnd") +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt index d59f1f5bbe25..f73b6cd156f3 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt @@ -28,6 +28,7 @@ import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene @@ -39,6 +40,10 @@ object Bouncer { val Background = ElementKey("BouncerBackground") val Content = ElementKey("BouncerContent") } + + object TestTags { + const val Root = "bouncer_root" + } } /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */ @@ -78,7 +83,9 @@ private fun SceneScope.BouncerScene( BouncerContent( viewModel, dialogFactory, - Modifier.element(Bouncer.Elements.Content).fillMaxSize() + Modifier.sysuiResTag(Bouncer.TestTags.Root) + .element(Bouncer.Elements.Content) + .fillMaxSize() ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index c34f2fd26d0c..203bd7a69a7a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -27,7 +27,6 @@ import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi @@ -49,9 +48,11 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformIconButton import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel import com.android.systemui.common.ui.compose.SelectedUserAwareInputConnection +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.res.R /** UI for the input part of a password-requiring version of the bouncer. */ @@ -61,18 +62,20 @@ internal fun PasswordBouncer( modifier: Modifier = Modifier, ) { val focusRequester = remember { FocusRequester() } - val isTextFieldFocusRequested by viewModel.isTextFieldFocusRequested.collectAsState() + val isTextFieldFocusRequested by + viewModel.isTextFieldFocusRequested.collectAsStateWithLifecycle() LaunchedEffect(isTextFieldFocusRequested) { if (isTextFieldFocusRequested) { focusRequester.requestFocus() } } - val password: String by viewModel.password.collectAsState() - val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() - val animateFailure: Boolean by viewModel.animateFailure.collectAsState() - val isImeSwitcherButtonVisible by viewModel.isImeSwitcherButtonVisible.collectAsState() - val selectedUserId by viewModel.selectedUserId.collectAsState() + val password: String by viewModel.password.collectAsStateWithLifecycle() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle() + val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle() + val isImeSwitcherButtonVisible by + viewModel.isImeSwitcherButtonVisible.collectAsStateWithLifecycle() + val selectedUserId by viewModel.selectedUserId.collectAsStateWithLifecycle() DisposableEffect(Unit) { onDispose { viewModel.onHidden() } } @@ -105,6 +108,7 @@ internal fun PasswordBouncer( ), modifier = modifier + .sysuiResTag("bouncer_text_entry") .focusRequester(focusRequester) .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } .drawBehind { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index 7af8408938a0..069113b378da 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -17,6 +17,7 @@ package com.android.systemui.bouncer.ui.composable import android.view.HapticFeedbackConstants +import androidx.annotation.VisibleForTesting import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.tween @@ -30,7 +31,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -47,16 +47,24 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.integerResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Easings import com.android.compose.modifiers.thenIf import com.android.internal.R +import com.android.systemui.bouncer.ui.composable.MotionTestKeys.dotAppearFadeIn +import com.android.systemui.bouncer.ui.composable.MotionTestKeys.dotAppearMoveUp +import com.android.systemui.bouncer.ui.composable.MotionTestKeys.dotScaling +import com.android.systemui.bouncer.ui.composable.MotionTestKeys.entryCompleted import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel +import com.android.systemui.compose.modifiers.sysuiResTag import kotlin.math.min import kotlin.math.pow import kotlin.math.sqrt import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch +import platform.test.motion.compose.values.MotionTestValueKey +import platform.test.motion.compose.values.motionTestValues /** * UI for the input part of a pattern-requiring version of the bouncer. @@ -67,7 +75,8 @@ import kotlinx.coroutines.launch * `false`, the dots will be pushed towards the end/bottom of the axis. */ @Composable -internal fun PatternBouncer( +@VisibleForTesting +fun PatternBouncer( viewModel: PatternBouncerViewModel, centerDotsVertically: Boolean, modifier: Modifier = Modifier, @@ -85,14 +94,15 @@ internal fun PatternBouncer( val lineStrokeWidth = with(density) { LINE_STROKE_WIDTH_DP.dp.toPx() } // All dots that should be rendered on the grid. - val dots: List<PatternDotViewModel> by viewModel.dots.collectAsState() + val dots: List<PatternDotViewModel> by viewModel.dots.collectAsStateWithLifecycle() // The most recently selected dot, if the user is currently dragging. - val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsState() + val currentDot: PatternDotViewModel? by viewModel.currentDot.collectAsStateWithLifecycle() // The dots selected so far, if the user is currently dragging. - val selectedDots: List<PatternDotViewModel> by viewModel.selectedDots.collectAsState() - val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() - val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsState() - val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + val selectedDots: List<PatternDotViewModel> by + viewModel.selectedDots.collectAsStateWithLifecycle() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle() + val isAnimationEnabled: Boolean by viewModel.isPatternVisible.collectAsStateWithLifecycle() + val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle() // Map of animatables for the scale of each dot, keyed by dot. val dotScalingAnimatables = remember(dots) { dots.associateWith { Animatable(1f) } } @@ -109,33 +119,11 @@ internal fun PatternBouncer( remember(dots) { dots.associateWith { dot -> with(density) { (80 + (20 * dot.y)).dp.toPx() } } } + + var entryAnimationCompleted by remember { mutableStateOf(false) } LaunchedEffect(Unit) { - dotAppearFadeInAnimatables.forEach { (dot, animatable) -> - scope.launch { - animatable.animateTo( - targetValue = 1f, - animationSpec = - tween( - delayMillis = 33 * dot.y, - durationMillis = 450, - easing = Easings.LegacyDecelerate, - ) - ) - } - } - dotAppearMoveUpAnimatables.forEach { (dot, animatable) -> - scope.launch { - animatable.animateTo( - targetValue = 1f, - animationSpec = - tween( - delayMillis = 0, - durationMillis = 450 + (33 * dot.y), - easing = Easings.StandardDecelerate, - ) - ) - } - } + showEntryAnimation(dotAppearFadeInAnimatables, dotAppearMoveUpAnimatables) + entryAnimationCompleted = true } val view = LocalView.current @@ -234,6 +222,7 @@ internal fun PatternBouncer( Canvas( modifier + .sysuiResTag("bouncer_pattern_root") // Because the width also includes spacing to the left and right of the leftmost and // rightmost dots in the grid and because UX mocks specify the width without that // spacing, the actual width needs to be defined slightly bigger than the UX mock width. @@ -283,6 +272,12 @@ internal fun PatternBouncer( } } } + .motionTestValues { + entryAnimationCompleted exportAs entryCompleted + dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn + dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp + dotScalingAnimatables.map { it.value.value } exportAs dotScaling + } ) { gridCoordinates?.let { nonNullCoordinates -> val containerSize = nonNullCoordinates.size @@ -378,6 +373,40 @@ internal fun PatternBouncer( } } +private suspend fun showEntryAnimation( + dotAppearFadeInAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>, + dotAppearMoveUpAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>, +) { + coroutineScope { + dotAppearFadeInAnimatables.forEach { (dot, animatable) -> + launch { + animatable.animateTo( + targetValue = 1f, + animationSpec = + tween( + delayMillis = 33 * dot.y, + durationMillis = 450, + easing = Easings.LegacyDecelerate, + ) + ) + } + } + dotAppearMoveUpAnimatables.forEach { (dot, animatable) -> + launch { + animatable.animateTo( + targetValue = 1f, + animationSpec = + tween( + delayMillis = 0, + durationMillis = 450 + (33 * dot.y), + easing = Easings.StandardDecelerate, + ) + ) + } + } + } +} + /** Returns an [Offset] representation of the given [dot], in pixel coordinates. */ private fun pixelOffset( dot: PatternDotViewModel, @@ -487,3 +516,11 @@ private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 0.81f). private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50 private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33 private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617 + +@VisibleForTesting +object MotionTestKeys { + val entryCompleted = MotionTestValueKey<Boolean>("PinBouncer::entryAnimationCompleted") + val dotAppearFadeIn = MotionTestValueKey<List<Float>>("PinBouncer::dotAppearFadeIn") + val dotAppearMoveUp = MotionTestValueKey<List<Float>>("PinBouncer::dotAppearMoveUp") + val dotScaling = MotionTestValueKey<List<Float>>("PinBouncer::dotScaling") +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 5651a4646b2d..64ace2f18372 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -33,7 +33,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -49,6 +48,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid import com.android.compose.modifiers.thenIf @@ -74,12 +74,13 @@ fun PinPad( ) { DisposableEffect(Unit) { onDispose { viewModel.onHidden() } } - val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsState() - val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState() - val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState() - val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + val isInputEnabled: Boolean by viewModel.isInputEnabled.collectAsStateWithLifecycle() + val backspaceButtonAppearance by + viewModel.backspaceButtonAppearance.collectAsStateWithLifecycle() + val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsStateWithLifecycle() + val animateFailure: Boolean by viewModel.animateFailure.collectAsStateWithLifecycle() val isDigitButtonAnimationEnabled: Boolean by - viewModel.isDigitButtonAnimationEnabled.collectAsState() + viewModel.isDigitButtonAnimationEnabled.collectAsStateWithLifecycle() val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } } LaunchedEffect(animateFailure) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt index 1a97912c77bb..465eade4e169 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt @@ -42,7 +42,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateListOf @@ -65,6 +64,7 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformOutlinedButton import com.android.compose.animation.Easings import com.android.keyguard.PinShapeAdapter @@ -86,7 +86,7 @@ fun PinInputDisplay( viewModel: PinBouncerViewModel, modifier: Modifier = Modifier, ) { - val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsState() + val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsStateWithLifecycle() val shapeAnimations = rememberShapeAnimations(viewModel.pinShapes) // The display comes in two different flavors: @@ -119,7 +119,7 @@ private fun HintingPinInputDisplay( hintedPinLength: Int, modifier: Modifier = Modifier, ) { - val pinInput: PinInputViewModel by viewModel.pinInput.collectAsState() + val pinInput: PinInputViewModel by viewModel.pinInput.collectAsStateWithLifecycle() // [ClearAll] marker pointing at the beginning of the current pin input. // When a new [ClearAll] token is added to the [pinInput], the clear-all animation is played // and the marker is advanced manually to the most recent marker. See LaunchedEffect below. @@ -257,9 +257,10 @@ private fun RegularPinInputDisplay( @Composable private fun SimArea(viewModel: PinBouncerViewModel) { - val isLockedEsim by viewModel.isLockedEsim.collectAsState() - val isSimUnlockingDialogVisible by viewModel.isSimUnlockingDialogVisible.collectAsState() - val errorDialogMessage by viewModel.errorDialogMessage.collectAsState() + val isLockedEsim by viewModel.isLockedEsim.collectAsStateWithLifecycle() + val isSimUnlockingDialogVisible by + viewModel.isSimUnlockingDialogVisible.collectAsStateWithLifecycle() + val errorDialogMessage by viewModel.errorDialogMessage.collectAsStateWithLifecycle() var unlockDialog: Dialog? by remember { mutableStateOf(null) } var errorDialog: Dialog? by remember { mutableStateOf(null) } val context = LocalView.current.context diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt index c8e145034551..694326d0549c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/SelectedUserAwareInputConnection.kt @@ -35,7 +35,7 @@ import androidx.compose.ui.platform.PlatformTextInputMethodRequest * ``` * @Composable * fun YourFunction(viewModel: YourViewModel) { - * val selectedUserId by viewModel.selectedUserId.collectAsState() + * val selectedUserId by viewModel.selectedUserId.collectAsStateWithLifecycle() * * SelectedUserAwareInputConnection(selectedUserId) { * TextField(...) 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 8dda067d8c8a..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 @@ -16,6 +16,7 @@ package com.android.systemui.common.ui.compose.windowinsets +import android.view.DisplayCutout as ViewDisplayCutout import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import kotlin.math.abs @@ -27,8 +28,14 @@ data class DisplayCutout( val right: Dp = 0.dp, val bottom: Dp = 0.dp, val location: CutoutLocation = CutoutLocation.NONE, + /** + * The original `DisplayCutout` for the `View` world; only use this when feeding it back to a + * `View`. + */ + 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/common/ui/compose/windowinsets/ScreenDecorProvider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt index 8144d15020fa..296fc27ac0ff 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/windowinsets/ScreenDecorProvider.kt @@ -22,12 +22,12 @@ import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.systemBars import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.flow.StateFlow /** The bounds and [CutoutLocation] of the current display. */ @@ -45,7 +45,7 @@ fun ScreenDecorProvider( screenCornerRadius: Float, content: @Composable () -> Unit, ) { - val cutout by displayCutout.collectAsState() + val cutout by displayCutout.collectAsStateWithLifecycle() val screenCornerRadiusDp = with(LocalDensity.current) { screenCornerRadius.toDp() } val density = LocalDensity.current 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..feb1f5b17bef 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 @@ -2,19 +2,28 @@ package com.android.systemui.communal.ui.compose import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle 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 @@ -25,6 +34,7 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions +import com.android.systemui.Flags import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys import com.android.systemui.communal.ui.compose.extensions.allowGestures @@ -33,33 +43,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,12 +90,15 @@ 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) - val touchesAllowed by viewModel.touchesAllowed.collectAsState(initial = false) + val currentSceneKey: SceneKey by + viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank) + val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false) + val showGestureIndicator by + viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false) val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( initialScene = currentSceneKey, @@ -119,7 +137,19 @@ fun CommunalContainer( ) ) { // This scene shows nothing only allowing for transitions to the communal scene. - Box(modifier = Modifier.fillMaxSize()) + // TODO(b/339667383): remove this temporary swipe gesture handle + Row(modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.End) { + if (showGestureIndicator && Flags.glanceableHubGestureHandle()) { + Box( + modifier = + Modifier.height(220.dp) + .width(4.dp) + .align(Alignment.CenterVertically) + .background(color = Color.White, RoundedCornerShape(4.dp)) + ) + Spacer(modifier = Modifier.width(12.dp)) + } + } } scene( @@ -127,7 +157,7 @@ fun CommunalContainer( userActions = mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank) ) { - CommunalScene(viewModel, colors, dialogFactory, modifier = modifier) + CommunalScene(colors, content) } } @@ -139,12 +169,11 @@ 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() + val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle() Box( modifier = @@ -152,7 +181,5 @@ private fun SceneScope.CommunalScene( .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 a592aa95195f..cd27d5713c2d 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 @@ -48,6 +48,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredSize import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight @@ -59,7 +60,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 @@ -77,7 +79,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -87,8 +88,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.draw.scale import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter @@ -107,7 +106,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 @@ -121,9 +119,11 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.times import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup -import androidx.core.view.setPadding +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowMetricsCalculator import com.android.compose.modifiers.thenIf import com.android.compose.theme.LocalAndroidColorScheme @@ -156,20 +156,21 @@ fun CommunalHub( onOpenWidgetPicker: (() -> Unit)? = null, onEditDone: (() -> Unit)? = null, ) { - val communalContent by viewModel.communalContent.collectAsState(initial = emptyList()) - val currentPopup by viewModel.currentPopup.collectAsState(initial = null) + val communalContent by + viewModel.communalContent.collectAsStateWithLifecycle(initialValue = emptyList()) + val currentPopup by viewModel.currentPopup.collectAsStateWithLifecycle(initialValue = null) var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var toolbarSize: IntSize? by remember { mutableStateOf(null) } var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) } var isDraggingToRemove by remember { mutableStateOf(false) } val gridState = rememberLazyGridState() val contentListState = rememberContentListState(widgetConfigurator, communalContent, viewModel) - val reorderingWidgets by viewModel.reorderingWidgets.collectAsState() - val selectedKey = viewModel.selectedKey.collectAsState() + val reorderingWidgets by viewModel.reorderingWidgets.collectAsStateWithLifecycle() + val selectedKey = viewModel.selectedKey.collectAsStateWithLifecycle() val removeButtonEnabled by remember { derivedStateOf { selectedKey.value != null || reorderingWidgets } } - val isEmptyState by viewModel.isEmptyState.collectAsState(initial = false) + val isEmptyState by viewModel.isEmptyState.collectAsStateWithLifecycle(initialValue = false) val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) val contentOffset = beforeContentPadding(contentPadding).toOffset() @@ -264,20 +265,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, @@ -315,9 +304,9 @@ fun CommunalHub( if (viewModel is CommunalViewModel && dialogFactory != null) { val isEnableWidgetDialogShowing by - viewModel.isEnableWidgetDialogShowing.collectAsState(false) + viewModel.isEnableWidgetDialogShowing.collectAsStateWithLifecycle(false) val isEnableWorkProfileDialogShowing by - viewModel.isEnableWorkProfileDialogShowing.collectAsState(false) + viewModel.isEnableWorkProfileDialogShowing.collectAsStateWithLifecycle(false) EnableWidgetDialog( isEnableWidgetDialogVisible = isEnableWidgetDialogShowing, @@ -438,8 +427,8 @@ private fun BoxScope.CommunalHubLazyGrid( state = gridState, rows = GridCells.Fixed(CommunalContentSize.FULL.span), contentPadding = contentPadding, - horizontalArrangement = Arrangement.spacedBy(32.dp), - verticalArrangement = Arrangement.spacedBy(32.dp), + horizontalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), + verticalArrangement = Arrangement.spacedBy(Dimensions.ItemSpacing), ) { items( count = list.size, @@ -452,7 +441,7 @@ private fun BoxScope.CommunalHubLazyGrid( Dimensions.CardWidth.value, list[index].size.dp().value, ) - val cardModifier = Modifier.size(width = size.width.dp, height = size.height.dp) + val cardModifier = Modifier.requiredSize(width = size.width.dp, height = size.height.dp) if (viewModel.isEditMode && dragDropState != null) { val selected by remember(index) { derivedStateOf { list[index].key == selectedKey.value } } @@ -469,6 +458,8 @@ private fun BoxScope.CommunalHubLazyGrid( size = size, selected = selected && !isDragging, widgetConfigurator = widgetConfigurator, + index = index, + contentListState = contentListState ) } } else { @@ -478,6 +469,8 @@ private fun BoxScope.CommunalHubLazyGrid( viewModel = viewModel, size = size, selected = false, + index = index, + contentListState = contentListState ) } } @@ -545,26 +538,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 @@ -573,7 +546,6 @@ private fun LockStateIcon( */ @Composable private fun Toolbar( - isDraggingToRemove: Boolean, removeEnabled: Boolean, onRemoveClicked: () -> Unit, setToolbarSize: (toolbarSize: IntSize) -> Unit, @@ -587,7 +559,7 @@ private fun Toolbar( label = "RemoveButtonAlphaAnimation" ) - Row( + Box( modifier = Modifier.fillMaxWidth() .padding( @@ -596,65 +568,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), + ) + } } } } @@ -758,7 +719,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), @@ -782,13 +743,26 @@ private fun CommunalContent( selected: Boolean, modifier: Modifier = Modifier, widgetConfigurator: WidgetConfigurator? = null, + index: Int, + contentListState: ContentListState, ) { when (model) { is CommunalContentModel.WidgetContent.Widget -> - WidgetContent(viewModel, model, size, selected, widgetConfigurator, modifier) + WidgetContent( + viewModel, + model, + size, + selected, + widgetConfigurator, + modifier, + index, + contentListState + ) 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) @@ -821,12 +795,10 @@ private fun CtaTileInViewModeContent( containerColor = colors.primary, contentColor = colors.onPrimary, ), - shape = RoundedCornerShape(80.dp, 40.dp, 80.dp, 40.dp) + shape = RoundedCornerShape(68.dp, 34.dp, 68.dp, 34.dp) ) { Column( - modifier = Modifier.fillMaxSize().padding(horizontal = 82.dp), - verticalArrangement = - Arrangement.spacedBy(Dimensions.Spacing, Alignment.CenterVertically), + modifier = Modifier.fillMaxSize().padding(vertical = 38.dp, horizontal = 70.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { Icon( @@ -834,11 +806,13 @@ private fun CtaTileInViewModeContent( contentDescription = stringResource(R.string.cta_label_to_open_widget_picker), modifier = Modifier.size(Dimensions.IconSize), ) + Spacer(modifier = Modifier.size(6.dp)) Text( text = stringResource(R.string.cta_label_to_edit_widget), - style = MaterialTheme.typography.titleLarge, + style = MaterialTheme.typography.titleMedium, textAlign = TextAlign.Center, ) + Spacer(modifier = Modifier.size(20.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, @@ -854,9 +828,10 @@ private fun CtaTileInViewModeContent( ) { Text( text = stringResource(R.string.cta_tile_button_to_dismiss), + fontSize = 12.sp, ) } - Spacer(modifier = Modifier.size(Dimensions.Spacing)) + Spacer(modifier = Modifier.size(14.dp)) Button( colors = ButtonDefaults.buttonColors( @@ -868,6 +843,7 @@ private fun CtaTileInViewModeContent( ) { Text( text = stringResource(R.string.cta_tile_button_to_open_widget_editor), + fontSize = 12.sp, ) } } @@ -883,14 +859,21 @@ private fun WidgetContent( selected: Boolean, widgetConfigurator: WidgetConfigurator?, modifier: Modifier = Modifier, + index: Int, + contentListState: ContentListState, ) { val context = LocalContext.current - val isFocusable by viewModel.isFocusable.collectAsState(initial = false) + val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false) val accessibilityLabel = remember(model, context) { model.providerInfo.loadLabel(context.packageManager).toString().trim() } val clickActionLabel = stringResource(R.string.accessibility_action_label_select_widget) + val removeWidgetActionLabel = stringResource(R.string.accessibility_action_label_remove_widget) + val placeWidgetActionLabel = stringResource(R.string.accessibility_action_label_place_widget) + val selectedKey by viewModel.selectedKey.collectAsStateWithLifecycle() + val selectedIndex = + selectedKey?.let { key -> contentListState.list.indexOfFirst { it.key == key } } Box( modifier = modifier @@ -907,6 +890,36 @@ private fun WidgetContent( Modifier.semantics { contentDescription = accessibilityLabel onClick(label = clickActionLabel, action = null) + 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 + } + ) + } + + customActions = actions } } ) { @@ -916,10 +929,14 @@ private fun WidgetContent( model.appWidgetHost .createViewForCommunal(context, model.appWidgetId, model.providerInfo) .apply { - updateAppWidgetSize(Bundle.EMPTY, listOf(size)) - // Remove the extra padding applied to AppWidgetHostView to allow widgets to - // occupy the entire box. - setPadding(0) + updateAppWidgetSize( + /* newOptions = */ Bundle(), + /* minWidth = */ size.width.toInt(), + /* minHeight = */ size.height.toInt(), + /* maxWidth = */ size.width.toInt(), + /* maxHeight = */ size.height.toInt(), + /* ignorePadding = */ true + ) accessibilityDelegate = viewModel.widgetAccessibilityDelegate } }, @@ -1022,13 +1039,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, @@ -1069,7 +1116,7 @@ private fun Umo(viewModel: BaseCommunalViewModel, modifier: Modifier = Modifier) @Composable fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composable () -> Unit) { val context = LocalContext.current - val isFocusable by viewModel.isFocusable.collectAsState(initial = false) + val isFocusable by viewModel.isFocusable.collectAsStateWithLifecycle(initialValue = false) Box( modifier = Modifier.fillMaxWidth().wrapContentHeight().thenIf( @@ -1112,7 +1159,11 @@ fun AccessibilityContainer(viewModel: BaseCommunalViewModel, content: @Composabl @Composable private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues { if (!isEditMode || toolbarSize == null) { - return PaddingValues(start = 48.dp, end = 48.dp, top = Dimensions.GridTopSpacing) + return PaddingValues( + start = Dimensions.ItemSpacing, + end = Dimensions.ItemSpacing, + top = Dimensions.GridTopSpacing, + ) } val context = LocalContext.current val density = LocalDensity.current @@ -1175,18 +1226,19 @@ data class ContentPaddingInPx(val start: Float, val top: Float) { } object Dimensions { - val CardWidth = 424.dp - val CardHeightFull = 596.dp - val CardHeightHalf = 282.dp - val CardHeightThird = 177.33.dp - val CardOutlineWidth = 3.dp - val GridTopSpacing = 64.dp + val CardHeightFull = 530.dp + val GridTopSpacing = 114.dp val GridHeight = CardHeightFull + GridTopSpacing - val Spacing = 16.dp + val ItemSpacing = 50.dp + val CardHeightHalf = (CardHeightFull - ItemSpacing) / 2 + val CardHeightThird = (CardHeightFull - (2 * ItemSpacing)) / 3 + val CardWidth = 360.dp + val CardOutlineWidth = 3.dp + val Spacing = ItemSpacing / 2 // The sizing/padding of the toolbar in glanceable hub edit mode val ToolbarPaddingTop = 27.dp - val ToolbarPaddingHorizontal = 16.dp + val ToolbarPaddingHorizontal = ItemSpacing val ToolbarButtonPaddingHorizontal = 24.dp val ToolbarButtonPaddingVertical = 16.dp val ButtonPadding = @@ -1194,10 +1246,7 @@ object Dimensions { vertical = ToolbarButtonPaddingVertical, horizontal = ToolbarButtonPaddingHorizontal, ) - val IconSize = 48.dp - - val LockIconSize = 52.dp - val LockIconBottomPadding = 70.dp + val IconSize = 40.dp } private object Colors { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt index e77ade91a93b..17dac7e2e6d5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/fold/ui/composable/FoldPosture.kt @@ -18,11 +18,11 @@ package com.android.systemui.fold.ui.composable import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.window.layout.WindowInfoTracker import com.android.systemui.fold.ui.helper.FoldPosture import com.android.systemui.fold.ui.helper.foldPostureInternal @@ -32,7 +32,8 @@ import com.android.systemui.fold.ui.helper.foldPostureInternal fun foldPosture(): State<FoldPosture> { val context = LocalContext.current val infoTracker = remember(context) { WindowInfoTracker.getOrCreate(context) } - val layoutInfo by infoTracker.windowLayoutInfo(context).collectAsState(initial = null) + val layoutInfo by + infoTracker.windowLayoutInfo(context).collectAsStateWithLifecycle(initialValue = null) return produceState<FoldPosture>( initialValue = FoldPosture.Folded, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt index a8d801abdcd0..67840c7fc696 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyboard/stickykeys/ui/view/StickyKeysIndicator.kt @@ -29,7 +29,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.ui.Alignment @@ -37,6 +36,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.PlatformTheme import com.android.systemui.keyboard.stickykeys.shared.model.Locked import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey @@ -57,7 +57,7 @@ fun createStickyKeyIndicatorView(context: Context, viewModel: StickyKeysIndicato @Composable fun StickyKeysIndicator(viewModel: StickyKeysIndicatorViewModel) { - val stickyKeys by viewModel.indicatorContent.collectAsState(emptyMap()) + val stickyKeys by viewModel.indicatorContent.collectAsStateWithLifecycle(emptyMap()) StickyKeysIndicator(stickyKeys) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt index 4bef9efe79d1..ca4ff837daa6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt @@ -18,12 +18,13 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel @@ -51,7 +52,7 @@ constructor( modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() - val blueprintId by viewModel.blueprintId(coroutineScope).collectAsState() + val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle() val view = LocalView.current DisposableEffect(view) { clockInteractor.clockEventController.registerListeners(view) @@ -60,6 +61,6 @@ constructor( } val blueprint = blueprintByBlueprintId[blueprintId] ?: return - with(blueprint) { Content(modifier) } + with(blueprint) { Content(modifier.sysuiResTag("keyguard_root_view")) } } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt index 472484aa74d9..4555f13a1a2c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt @@ -26,13 +26,13 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.input.pointer.pointerInput +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel /** Container for lockscreen content that handles long-press to bring up the settings menu. */ @@ -42,7 +42,8 @@ fun LockscreenLongPress( modifier: Modifier = Modifier, content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit, ) { - val isEnabled: Boolean by viewModel.isLongPressHandlingEnabled.collectAsState(initial = false) + val isEnabled: Boolean by + viewModel.isLongPressHandlingEnabled.collectAsStateWithLifecycle(initialValue = false) val (settingsMenuBounds, setSettingsMenuBounds) = remember { mutableStateOf<Rect?>(null) } val interactionSource = remember { MutableInteractionSource() } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt index 8129e41b4977..ba25719f1d60 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt @@ -21,11 +21,11 @@ import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalDensity +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.plugins.clocks.ClockController @@ -37,7 +37,7 @@ import kotlin.math.roundToInt fun rememberBurnIn( clockInteractor: KeyguardClockInteractor, ): BurnInState { - val clock by clockInteractor.currentClock.collectAsState() + val clock by clockInteractor.currentClock.collectAsStateWithLifecycle() val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) } val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt index 315253524b61..a39fa64dd45b 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt @@ -22,15 +22,16 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntRect +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.keyguard.ui.composable.LockscreenLongPress import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection @@ -67,8 +68,8 @@ constructor( override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible val shouldUseSplitNotificationShade by - viewModel.shouldUseSplitNotificationShade.collectAsState() - val unfoldTranslations by viewModel.unfoldTranslations.collectAsState() + viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle() + val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() LockscreenLongPress( viewModel = viewModel.longPress, @@ -129,7 +130,7 @@ constructor( with(lockSection) { LockIcon() } // Aligned to bottom and constrained to below the lock icon. - Column(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.fillMaxWidth().sysuiResTag("keyguard_bottom_area")) { if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) { with(ambientIndicationSectionOptional.get()) { AmbientIndication(modifier = Modifier.fillMaxWidth()) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt index 9d31955122eb..c83f62c4281c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt @@ -22,13 +22,13 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize 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.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.Layout import androidx.compose.ui.unit.IntRect +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.keyguard.ui.composable.LockscreenLongPress @@ -70,8 +70,8 @@ constructor( override fun SceneScope.Content(modifier: Modifier) { val isUdfpsVisible = viewModel.isUdfpsVisible val shouldUseSplitNotificationShade by - viewModel.shouldUseSplitNotificationShade.collectAsState() - val unfoldTranslations by viewModel.unfoldTranslations.collectAsState() + viewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle() + val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle() LockscreenLongPress( viewModel = viewModel.longPress, 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..aaf49ff00aca 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 @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.ui.composable.modifier import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -25,6 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onPlaced +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel @@ -44,8 +44,10 @@ fun Modifier.burnInAware( val translationYState = remember { mutableStateOf(0F) } val copiedParams = params.copy(translationY = { translationYState.value }) val burnIn = viewModel.movement(copiedParams) - val translationX by burnIn.map { it.translationX.toFloat() }.collectAsState(initial = 0f) - val translationY by burnIn.map { it.translationY.toFloat() }.collectAsState(initial = 0f) + val translationX by + burnIn.map { it.translationX.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f) + val translationY by + burnIn.map { it.translationY.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f) translationYState.value = translationY val scaleViewModel by burnIn @@ -55,18 +57,14 @@ fun Modifier.burnInAware( scaleClockOnly = it.scaleClockOnly, ) } - .collectAsState(initial = BurnInScaleViewModel()) + .collectAsStateWithLifecycle(initialValue = 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/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index 09ec76df3aea..218779da20b9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -25,13 +25,13 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.contains +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.customization.R @@ -59,9 +59,11 @@ constructor( onTopChanged: (top: Float?) -> Unit, modifier: Modifier = Modifier, ) { - val currentClock by viewModel.currentClock.collectAsState() + val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() val smallTopMargin by - viewModel.smallClockTopMargin.collectAsState(viewModel.getSmallClockTopMargin()) + viewModel.smallClockTopMargin.collectAsStateWithLifecycle( + viewModel.getSmallClockTopMargin() + ) if (currentClock?.smallClock?.view == null) { return } @@ -89,7 +91,7 @@ constructor( @Composable fun SceneScope.LargeClock(burnInParams: BurnInParameters, modifier: Modifier = Modifier) { - val currentClock by viewModel.currentClock.collectAsState() + val currentClock by viewModel.currentClock.collectAsStateWithLifecycle() if (currentClock?.largeClock?.view == null) { return } 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..3ca2b9c1d86c 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.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.collectAsStateWithLifecycle() + MediaCarousel( - isVisible = ::isVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt index f48fa88b9722..7f80dfa703bc 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt @@ -20,13 +20,13 @@ import android.view.ViewGroup import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.thenIf import com.android.systemui.Flags @@ -40,16 +40,19 @@ import com.android.systemui.notifications.ui.composable.ConstrainedNotificationS import com.android.systemui.res.R import com.android.systemui.shade.LargeScreenHeaderHelper import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel +import dagger.Lazy import javax.inject.Inject @SysUISingleton class NotificationSection @Inject constructor( + private val stackScrollView: Lazy<NotificationScrollView>, private val viewModel: NotificationsPlaceholderViewModel, private val aodBurnInViewModel: AodBurnInViewModel, sharedNotificationContainer: SharedNotificationContainer, @@ -88,9 +91,9 @@ constructor( @Composable fun SceneScope.Notifications(burnInParams: BurnInParameters?, modifier: Modifier = Modifier) { val shouldUseSplitNotificationShade by - lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsState() + lockscreenContentViewModel.shouldUseSplitNotificationShade.collectAsStateWithLifecycle() val areNotificationsVisible by - lockscreenContentViewModel.areNotificationsVisible.collectAsState() + lockscreenContentViewModel.areNotificationsVisible.collectAsStateWithLifecycle() val splitShadeTopMargin: Dp = if (Flags.centralizedStatusBarHeightFix()) { LargeScreenHeaderHelper.getLargeScreenHeaderHeight(LocalContext.current).dp @@ -103,6 +106,7 @@ constructor( } ConstrainedNotificationStack( + stackScrollView = stackScrollView.get(), viewModel = viewModel, modifier = modifier diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt index fc8b3b9009ce..44bda956b9f6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt @@ -26,7 +26,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -34,6 +33,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding import com.android.systemui.keyguard.KeyguardUnlockAnimationController @@ -160,7 +160,7 @@ constructor( private fun Weather( modifier: Modifier = Modifier, ) { - val isVisible by keyguardSmartspaceViewModel.isWeatherVisible.collectAsState() + val isVisible by keyguardSmartspaceViewModel.isWeatherVisible.collectAsStateWithLifecycle() if (!isVisible) { return } @@ -187,7 +187,7 @@ constructor( private fun Date( modifier: Modifier = Modifier, ) { - val isVisible by keyguardSmartspaceViewModel.isDateVisible.collectAsState() + val isVisible by keyguardSmartspaceViewModel.isDateVisible.collectAsStateWithLifecycle() if (!isVisible) { return } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt index ddc12ff22d50..d996d25eff20 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt @@ -23,6 +23,7 @@ import android.view.ViewGroup import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp @@ -31,6 +32,7 @@ import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.height import com.android.keyguard.dagger.KeyguardStatusBarViewComponent +import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.shade.ShadeViewStateProvider @@ -48,6 +50,31 @@ constructor( @Composable fun SceneScope.StatusBar(modifier: Modifier = Modifier) { val context = LocalContext.current + val viewDisplayCutout = LocalDisplayCutout.current.viewDisplayCutoutKeyguardStatusBarView + @SuppressLint("InflateParams") + val view = + remember(context) { + LayoutInflater.from(context) + .inflate( + R.layout.keyguard_status_bar, + null, + false, + ) as KeyguardStatusBarView + } + val viewController = + remember(view) { + val provider = + object : ShadeViewStateProvider { + override val lockscreenShadeDragProgress: Float = 0f + override val panelViewExpandedHeight: Float = 0f + + override fun shouldHeadsUpBeVisible(): Boolean { + return false + } + } + + componentFactory.build(view, provider).keyguardStatusBarViewController + } MovableElement( key = StatusBarElementKey, @@ -60,34 +87,14 @@ constructor( (it.parent as ViewGroup).removeView(it) } - val provider = - object : ShadeViewStateProvider { - override val lockscreenShadeDragProgress: Float = 0f - override val panelViewExpandedHeight: Float = 0f - - override fun shouldHeadsUpBeVisible(): Boolean { - return false - } - } - - @SuppressLint("InflateParams") - val view = - LayoutInflater.from(context) - .inflate( - R.layout.keyguard_status_bar, - null, - false, - ) as KeyguardStatusBarView - componentFactory - .build(view, provider) - .keyguardStatusBarViewController - .init() + viewController.init() view }, modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp).height { Utils.getStatusBarHeaderHeightKeyguard(context) }, + update = { viewController.setDisplayCutout(viewDisplayCutout) } ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt index 722032c19553..067315381773 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/TopAreaSection.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -33,6 +32,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.modifiers.thenIf @@ -61,9 +61,9 @@ constructor( fun DefaultClockLayout( modifier: Modifier = Modifier, ) { - val currentClockLayout by clockViewModel.currentClockLayout.collectAsState() + val currentClockLayout by clockViewModel.currentClockLayout.collectAsStateWithLifecycle() val hasCustomPositionUpdatedAnimation by - clockViewModel.hasCustomPositionUpdatedAnimation.collectAsState() + clockViewModel.hasCustomPositionUpdatedAnimation.collectAsStateWithLifecycle() val currentScene = when (currentClockLayout) { KeyguardClockViewModel.ClockLayout.SPLIT_SHADE_LARGE_CLOCK -> @@ -132,7 +132,7 @@ constructor( @Composable private fun SceneScope.LargeClockWithSmartSpace(shouldOffSetClockToOneHalf: Boolean = false) { val burnIn = rememberBurnIn(clockInteractor) - val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState() + val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle() LaunchedEffect(isLargeClockVisible) { if (isLargeClockVisible) { @@ -169,8 +169,8 @@ constructor( @Composable private fun SceneScope.WeatherLargeClockWithSmartSpace(modifier: Modifier = Modifier) { val burnIn = rememberBurnIn(clockInteractor) - val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsState() - val currentClockState = clockViewModel.currentClock.collectAsState() + val isLargeClockVisible by clockViewModel.isLargeClockVisible.collectAsStateWithLifecycle() + val currentClockState = clockViewModel.currentClock.collectAsStateWithLifecycle() LaunchedEffect(isLargeClockVisible) { if (isLargeClockVisible) { 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/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt index 2ba78cfd7785..fdf82ca026b1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt @@ -30,11 +30,15 @@ import com.android.compose.nestedscroll.PriorityNestedScrollConnection */ fun NotificationScrimNestedScrollConnection( scrimOffset: () -> Float, - onScrimOffsetChanged: (Float) -> Unit, + snapScrimOffset: (Float) -> Unit, + animateScrimOffset: (Float) -> Unit, minScrimOffset: () -> Float, maxScrimOffset: Float, contentHeight: () -> Float, minVisibleScrimHeight: () -> Float, + isCurrentGestureOverscroll: () -> Boolean, + onStart: (Float) -> Unit = {}, + onStop: (Float) -> Unit = {}, ): PriorityNestedScrollConnection { return PriorityNestedScrollConnection( orientation = Orientation.Vertical, @@ -49,7 +53,7 @@ fun NotificationScrimNestedScrollConnection( // scrolling down and content is done scrolling to top. After that, the scrim // needs to collapse; collapse the scrim until it is at the maxScrimOffset. canStartPostScroll = { offsetAvailable, _ -> - offsetAvailable > 0 && scrimOffset() < maxScrimOffset + offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll()) }, canStartPostFling = { false }, canContinueScroll = { @@ -57,7 +61,7 @@ fun NotificationScrimNestedScrollConnection( minScrimOffset() < currentHeight && currentHeight < maxScrimOffset }, canScrollOnFling = true, - onStart = { /* do nothing */}, + onStart = { offsetAvailable -> onStart(offsetAvailable) }, onScroll = { offsetAvailable -> val currentHeight = scrimOffset() val amountConsumed = @@ -68,10 +72,16 @@ fun NotificationScrimNestedScrollConnection( val amountLeft = minScrimOffset() - currentHeight offsetAvailable.coerceAtLeast(amountLeft) } - onScrimOffsetChanged(currentHeight + amountConsumed) + snapScrimOffset(currentHeight + amountConsumed) amountConsumed }, // Don't consume the velocity on pre/post fling - onStop = { 0f }, + onStop = { velocityAvailable -> + onStop(velocityAvailable) + if (scrimOffset() < minScrimOffset()) { + animateScrimOffset(minScrimOffset()) + } + 0f + }, ) } 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 6e987bd03483..cf2e895b044b 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 @@ -18,6 +18,8 @@ package com.android.systemui.notifications.ui.composable import android.util.Log +import androidx.compose.animation.core.Animatable +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.gestures.scrollBy import androidx.compose.foundation.layout.Box @@ -29,7 +31,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme @@ -37,10 +38,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +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 import androidx.compose.ui.Modifier @@ -62,21 +63,28 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.util.lerp +import androidx.lifecycle.compose.collectAsStateWithLifecycle 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 import kotlin.math.roundToInt +import kotlinx.coroutines.launch object Notifications { object Elements { @@ -104,7 +112,7 @@ fun SceneScope.HeadsUpNotificationSpace( modifier: Modifier = Modifier, isPeekFromBottom: Boolean = false, ) { - val headsUpHeight = viewModel.headsUpHeight.collectAsState() + val headsUpHeight = viewModel.headsUpHeight.collectAsStateWithLifecycle() Element( Notifications.Elements.HeadsUpNotificationPlaceholder, @@ -130,6 +138,7 @@ fun SceneScope.HeadsUpNotificationSpace( /** Adds the space where notification stack should appear in the scene. */ @Composable fun SceneScope.ConstrainedNotificationStack( + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { @@ -138,6 +147,7 @@ fun SceneScope.ConstrainedNotificationStack( modifier.onSizeChanged { viewModel.onConstrainedAvailableSpaceChanged(it.height) } ) { NotificationPlaceholder( + stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.fillMaxSize(), ) @@ -154,25 +164,40 @@ 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() val density = LocalDensity.current val screenCornerRadius = LocalScreenCornerRadius.current val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius) - val scrollState = rememberScrollState() - val syntheticScroll = viewModel.syntheticScroll.collectAsState(0f) - val expansionFraction by viewModel.expandFraction.collectAsState(0f) + val scrollState = + shadeSession.rememberSaveableSession(saver = ScrollState.Saver, key = null) { + ScrollState(initial = 0) + } + val syntheticScroll = viewModel.syntheticScroll.collectAsStateWithLifecycle(0f) + val isCurrentGestureOverscroll = + viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false) + val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f) val navBarHeight = 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()) + val scrimRounding = + viewModel.shadeScrimRounding.collectAsStateWithLifecycle(ShadeScrimRounding()) // the offset for the notifications scrim. Its upper bound is 0, and its lower bound is // calculated in minScrimOffset. The scrim is the same height as the screen minus the @@ -180,7 +205,7 @@ fun SceneScope.NotificationScrollingStack( // When fully expanded (scrimOffset = minScrimOffset), its top bound is at minScrimStartY, // which is equal to the height of the Shade Header. Thus, when the scrim is fully expanded, the // entire height of the scrim is visible on screen. - val scrimOffset = remember { mutableStateOf(0f) } + val scrimOffset = shadeSession.rememberSession { Animatable(0f) } // set the bounds to null when the scrim disappears DisposableEffect(Unit) { onDispose { viewModel.onScrimBoundsChanged(null) } } @@ -203,8 +228,8 @@ 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 } - .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.value = 0f } + snapshotFlow { stackHeight.intValue < minVisibleScrimHeight() && scrimOffset.value < 0f } + .collect { shouldCollapse -> if (shouldCollapse) scrimOffset.snapTo(0f) } } // if we receive scroll delta from NSSL, offset the scrim and placeholder accordingly. @@ -214,7 +239,7 @@ fun SceneScope.NotificationScrollingStack( val minOffset = minScrimOffset() if (scrimOffset.value > minOffset) { val remainingDelta = (minOffset - (scrimOffset.value - delta)).coerceAtLeast(0f) - scrimOffset.value = (scrimOffset.value - delta).coerceAtLeast(minOffset) + scrimOffset.snapTo((scrimOffset.value - delta).coerceAtLeast(minOffset)) if (remainingDelta > 0f) { scrollState.scrollBy(remainingDelta) } @@ -224,6 +249,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 @@ -292,30 +338,23 @@ fun SceneScope.NotificationScrollingStack( .debugBackground(viewModel, DEBUG_BOX_COLOR) ) { NotificationPlaceholder( + stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.verticalNestedScrollToScene( topBehavior = NestedScrollBehavior.EdgeWithPreview, + isExternalOverscrollGesture = { isCurrentGestureOverscroll.value } ) - .nestedScroll( - remember( - scrimOffset, - maxScrimTop, - minScrimTop, - ) { - NotificationScrimNestedScrollConnection( - scrimOffset = { scrimOffset.value }, - onScrimOffsetChanged = { scrimOffset.value = it }, - minScrimOffset = minScrimOffset, - maxScrimOffset = 0f, - contentHeight = { stackHeight.value }, - minVisibleScrimHeight = minVisibleScrimHeight, - ) - } - ) + .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) @@ -356,6 +395,7 @@ fun SceneScope.NotificationShelfSpace( @Composable private fun SceneScope.NotificationPlaceholder( + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, ) { @@ -374,10 +414,8 @@ private fun SceneScope.NotificationPlaceholder( " bounds=${coordinates.boundsInWindow()}" } // NOTE: positionInWindow.y scrolls off screen, but boundsInWindow.top will not - viewModel.onStackBoundsChanged( - top = positionInWindow.y, - bottom = positionInWindow.y + coordinates.size.height, - ) + stackScrollView.setStackTop(positionInWindow.y) + stackScrollView.setStackBottom(positionInWindow.y + coordinates.size.height) } ) { content {} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt new file mode 100644 index 000000000000..1c675e339941 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notifications.ui.composable + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.shade.ui.composable.OverlayShade +import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow + +@SysUISingleton +class NotificationsShadeScene +@Inject +constructor( + viewModel: NotificationsShadeSceneViewModel, + private val overlayShadeViewModel: OverlayShadeViewModel, +) : ComposableScene { + + override val key = Scenes.NotificationsShade + + override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = + viewModel.destinationScenes + + @Composable + override fun SceneScope.Content( + modifier: Modifier, + ) { + OverlayShade( + viewModel = overlayShadeViewModel, + modifier = modifier, + horizontalArrangement = Arrangement.Start, + ) { + Text( + text = "Notifications list", + modifier = Modifier.padding(NotificationsShade.Dimensions.Padding) + ) + } + } +} + +object NotificationsShade { + object Dimensions { + val Padding = 16.dp + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeSessionModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeSessionModule.kt new file mode 100644 index 000000000000..076c9177138b --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeSessionModule.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.notifications.ui.composable + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.saveable.Saver +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.session.shared.SessionStorage +import com.android.systemui.scene.session.ui.composable.SaveableSession +import com.android.systemui.scene.session.ui.composable.Session +import dagger.Module +import dagger.Provides + +@Module +object NotificationsShadeSessionModule { + @Provides @SysUISingleton fun provideShadeSessionStorage(): SessionStorage = SessionStorage() + + @Provides + fun provideShadeSession(storage: SessionStorage): SaveableSession = + object : SaveableSession, Session by Session(storage) { + @Composable + override fun <T : Any> rememberSaveableSession( + vararg inputs: Any?, + saver: Saver<T, out Any>, + key: String?, + init: () -> T + ): T = rememberSession(key, inputs = inputs, init = init) + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt index 73cb72ca062e..b808044b76ce 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.ui.Alignment @@ -46,6 +45,7 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel @@ -64,8 +64,8 @@ fun PeopleScreen( viewModel: PeopleViewModel, onResult: (PeopleViewModel.Result) -> Unit, ) { - val priorityTiles by viewModel.priorityTiles.collectAsState() - val recentTiles by viewModel.recentTiles.collectAsState() + val priorityTiles by viewModel.priorityTiles.collectAsStateWithLifecycle() + val recentTiles by viewModel.recentTiles.collectAsStateWithLifecycle() // Call [onResult] this activity when the ViewModel tells us so. LaunchedEffect(viewModel.result) { 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 eedff89e7a45..e8da4bd2d2cd 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 @@ -17,6 +17,11 @@ package com.android.systemui.qs.footer.ui.compose import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.tween +import androidx.compose.animation.expandVertically +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas import androidx.compose.foundation.LocalIndication @@ -39,7 +44,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -64,6 +68,7 @@ import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.compose.animation.Expandable import com.android.compose.animation.scene.SceneScope @@ -87,10 +92,24 @@ import kotlinx.coroutines.launch fun SceneScope.FooterActionsWithAnimatedVisibility( viewModel: FooterActionsViewModel, isCustomizing: Boolean, + customizingAnimationDuration: Int, lifecycleOwner: LifecycleOwner, modifier: Modifier = Modifier, ) { - AnimatedVisibility(visible = !isCustomizing, modifier = modifier.fillMaxWidth()) { + AnimatedVisibility( + visible = !isCustomizing, + enter = + expandVertically( + animationSpec = tween(customizingAnimationDuration), + initialHeight = { 0 }, + ) + fadeIn(tween(customizingAnimationDuration)), + exit = + shrinkVertically( + animationSpec = tween(customizingAnimationDuration), + targetHeight = { 0 }, + ) + fadeOut(tween(customizingAnimationDuration)), + modifier = modifier.fillMaxWidth() + ) { QuickSettingsTheme { // This view has its own horizontal padding // TODO(b/321716470) This should use a lifecycle tied to the scene. @@ -113,8 +132,8 @@ fun FooterActions( val context = LocalContext.current // Collect alphas as soon as we are composed, even when not visible. - val alpha by viewModel.alpha.collectAsState() - val backgroundAlpha = viewModel.backgroundAlpha.collectAsState() + val alpha by viewModel.alpha.collectAsStateWithLifecycle() + val backgroundAlpha = viewModel.backgroundAlpha.collectAsStateWithLifecycle() var security by remember { mutableStateOf<FooterActionsSecurityButtonViewModel?>(null) } var foregroundServices by remember { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt index ca6b3434d90e..73a624a030fe 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt @@ -21,13 +21,13 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.modifiers.height import com.android.compose.modifiers.width import com.android.systemui.qs.ui.adapter.QSSceneAdapter @@ -40,13 +40,13 @@ fun BrightnessMirror( qsSceneAdapter: QSSceneAdapter, modifier: Modifier = Modifier, ) { - val isShowing by viewModel.isShowing.collectAsState() + val isShowing by viewModel.isShowing.collectAsStateWithLifecycle() val mirrorAlpha by animateFloatAsState( targetValue = if (isShowing) 1f else 0f, label = "alphaAnimationBrightnessMirrorShowing", ) - val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsState() + val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsStateWithLifecycle() val offset = IntOffset(0, mirrorOffsetAndSize.yOffset) Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt index a87a8df290b7..54a98ddaa01a 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt @@ -22,18 +22,19 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.layout.layout import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.MovableElementScenePicker import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.TransitionState import com.android.compose.animation.scene.ValueKey import com.android.compose.modifiers.thenIf +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Companion.Collapsing import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding @@ -68,7 +69,7 @@ object QuickSettings { private fun SceneScope.stateForQuickSettingsContent( isSplitShade: Boolean, - squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default + squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default } ): QSSceneAdapter.State { return when (val transitionState = layoutState.transitionState) { is TransitionState.Idle -> { @@ -124,9 +125,9 @@ fun SceneScope.QuickSettings( heightProvider: () -> Int, isSplitShade: Boolean, modifier: Modifier = Modifier, - squishiness: Float = QuickSettings.SharedValues.SquishinessValues.Default, + squishiness: () -> Float = { QuickSettings.SharedValues.SquishinessValues.Default }, ) { - val contentState = stateForQuickSettingsContent(isSplitShade, squishiness) + val contentState = { stateForQuickSettingsContent(isSplitShade, squishiness) } val transitionState = layoutState.transitionState val isClosing = transitionState is TransitionState.Transition && @@ -143,7 +144,9 @@ fun SceneScope.QuickSettings( MovableElement( key = QuickSettings.Elements.Content, modifier = - modifier.fillMaxWidth().layout { measurable, constraints -> + modifier.sysuiResTag("quick_settings_panel").fillMaxWidth().layout { + measurable, + constraints -> val placeable = measurable.measure(constraints) // Use the height of the correct view based on the scene it is being composed in val height = heightProvider().coerceAtLeast(0) @@ -158,11 +161,14 @@ fun SceneScope.QuickSettings( @Composable private fun QuickSettingsContent( qsSceneAdapter: QSSceneAdapter, - state: QSSceneAdapter.State, + state: () -> QSSceneAdapter.State, modifier: Modifier = Modifier, ) { - val qsView by qsSceneAdapter.qsView.collectAsState(null) - val isCustomizing by qsSceneAdapter.isCustomizing.collectAsState() + val qsView by qsSceneAdapter.qsView.collectAsStateWithLifecycle(null) + val isCustomizing by + qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle( + qsSceneAdapter.isCustomizerShowing.value + ) QuickSettingsTheme { val context = LocalContext.current @@ -179,10 +185,10 @@ private fun QuickSettingsContent( AndroidView( modifier = Modifier.fillMaxWidth(), factory = { _ -> - qsSceneAdapter.setState(state) + qsSceneAdapter.setState(state()) view }, - update = { qsSceneAdapter.setState(state) } + update = { qsSceneAdapter.setState(state()) } ) } } 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 b33ea7845428..d76b19f3fa82 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 @@ -19,12 +19,15 @@ package com.android.systemui.qs.ui.composable import android.view.ViewGroup import androidx.activity.compose.BackHandler import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.animation.expandVertically import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.clipScrollableContainer import androidx.compose.foundation.gestures.Orientation @@ -33,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 @@ -45,8 +49,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -58,12 +62,15 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.TransitionState 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 @@ -76,16 +83,20 @@ import com.android.systemui.notifications.ui.composable.NotificationScrollingSta import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel 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 @@ -99,6 +110,8 @@ class QuickSettingsScene @Inject 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, @@ -121,6 +134,7 @@ constructor( modifier: Modifier, ) { QuickSettingsScene( + notificationStackScrollView = notificationStackScrollView.get(), viewModel = viewModel, notificationsPlaceholderViewModel = notificationsPlaceholderViewModel, createTintedIconManager = tintedIconManagerFactory::create, @@ -129,12 +143,14 @@ constructor( mediaCarouselController = mediaCarouselController, mediaHost = mediaHost, modifier = modifier, + shadeSession = shadeSession, ) } } @Composable private fun SceneScope.QuickSettingsScene( + notificationStackScrollView: NotificationScrollView, viewModel: QuickSettingsSceneViewModel, notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, @@ -143,14 +159,21 @@ private fun SceneScope.QuickSettingsScene( mediaCarouselController: MediaCarouselController, mediaHost: MediaHost, modifier: Modifier = Modifier, + shadeSession: SaveableSession, ) { - val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState() + val cutoutLocation = LocalDisplayCutout.current.location + + val brightnessMirrorShowing by + viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle() val contentAlpha by animateFloatAsState( targetValue = if (brightnessMirrorShowing) 0f else 1f, label = "alphaAnimationBrightnessMirrorContentHiding", ) + viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha) + DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } } + BrightnessMirror( viewModel = viewModel.brightnessMirrorViewModel, qsSceneAdapter = viewModel.qsSceneAdapter @@ -172,8 +195,15 @@ 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 isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() + val isCustomizerShowing by + viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() + val customizingAnimationDuration by + viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() val screenHeight = LocalRawScreenHeight.current BackHandler( @@ -213,6 +243,18 @@ private fun SceneScope.QuickSettingsScene( val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() val density = LocalDensity.current + val bottomPadding by + animateDpAsState( + targetValue = if (isCustomizing) 0.dp else navBarBottomHeight, + animationSpec = tween(customizingAnimationDuration), + label = "animateQSSceneBottomPaddingAsState" + ) + val topPadding by + animateDpAsState( + targetValue = if (isCustomizing) ShadeHeader.Dimensions.CollapsedHeight else 0.dp, + animationSpec = tween(customizingAnimationDuration), + label = "animateQSSceneTopPaddingAsState" + ) LaunchedEffect(navBarBottomHeight, density) { with(density) { @@ -232,17 +274,14 @@ private fun SceneScope.QuickSettingsScene( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxSize() - .then( - if (isCustomizing) { - Modifier.padding(top = 48.dp) - } else { - Modifier.padding(bottom = navBarBottomHeight) - } + .padding( + top = topPadding.coerceAtLeast(0.dp), + bottom = bottomPadding.coerceAtLeast(0.dp) ) ) { Box(modifier = Modifier.fillMaxSize().weight(1f)) { val shadeHeaderAndQuickSettingsModifier = - if (isCustomizing) { + if (isCustomizerShowing) { Modifier.fillMaxHeight().align(Alignment.TopCenter) } else { Modifier.verticalNestedScrollToScene() @@ -257,7 +296,8 @@ private fun SceneScope.QuickSettingsScene( } Column( - modifier = shadeHeaderAndQuickSettingsModifier, + modifier = + shadeHeaderAndQuickSettingsModifier.sysuiResTag("expanded_qs_scroll_view"), ) { when (LocalWindowSizeClass.current.widthSizeClass) { WindowWidthSizeClass.Compact -> @@ -265,15 +305,22 @@ private fun SceneScope.QuickSettingsScene( visible = !isCustomizing, enter = expandVertically( - animationSpec = tween(100), - initialHeight = { collapsedHeaderHeight }, - ) + fadeIn(tween(100)), + animationSpec = tween(customizingAnimationDuration), + expandFrom = Alignment.Top, + ) + + slideInVertically( + animationSpec = tween(customizingAnimationDuration), + ) + + fadeIn(tween(customizingAnimationDuration)), exit = shrinkVertically( - animationSpec = tween(100), - targetHeight = { collapsedHeaderHeight }, + animationSpec = tween(customizingAnimationDuration), shrinkTowards = Alignment.Top, - ) + fadeOut(tween(100)), + ) + + slideOutVertically( + animationSpec = tween(customizingAnimationDuration), + ) + + fadeOut(tween(customizingAnimationDuration)), ) { ExpandedShadeHeader( viewModel = viewModel.shadeHeaderViewModel, @@ -290,7 +337,6 @@ private fun SceneScope.QuickSettingsScene( createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, statusBarIconController = statusBarIconController, - modifier = Modifier.padding(horizontal = 16.dp), ) } Spacer(modifier = Modifier.height(16.dp)) @@ -299,11 +345,13 @@ private fun SceneScope.QuickSettingsScene( viewModel.qsSceneAdapter, { viewModel.qsSceneAdapter.qsHeight }, isSplitShade = false, - modifier = Modifier.sysuiResTag("expanded_qs_scroll_view"), + modifier = Modifier ) + val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() + MediaCarousel( - isVisible = viewModel::isMediaVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, @@ -314,14 +362,19 @@ private fun SceneScope.QuickSettingsScene( FooterActionsWithAnimatedVisibility( viewModel = footerActionsViewModel, isCustomizing = isCustomizing, + customizingAnimationDuration = customizingAnimationDuration, lifecycleOwner = lifecycleOwner, - modifier = Modifier.align(Alignment.CenterHorizontally), + modifier = + Modifier.align(Alignment.CenterHorizontally).sysuiResTag("qs_footer_actions"), ) } 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/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt new file mode 100644 index 000000000000..636c6c3b7d14 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.ui.composable + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.android.compose.animation.scene.SceneScope +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.shade.ui.composable.OverlayShade +import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel +import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow + +@SysUISingleton +class QuickSettingsShadeScene +@Inject +constructor( + viewModel: QuickSettingsShadeSceneViewModel, + private val overlayShadeViewModel: OverlayShadeViewModel, +) : ComposableScene { + + override val key = Scenes.QuickSettingsShade + + override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = + viewModel.destinationScenes + + @Composable + override fun SceneScope.Content( + modifier: Modifier, + ) { + OverlayShade( + viewModel = overlayShadeViewModel, + modifier = modifier, + horizontalArrangement = Arrangement.End, + ) { + Text( + text = "Quick settings grid", + modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding) + ) + } + } +} + +object QuickSettingsShade { + object Dimensions { + val Padding = 16.dp + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.kt new file mode 100644 index 000000000000..dc5891915bfc --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/shared/SessionStorage.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.scene.session.shared + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +/** Data store for [Session][com.android.systemui.scene.session.ui.composable.Session]. */ +class SessionStorage { + private var _storage by mutableStateOf(hashMapOf<String, StorageEntry>()) + + /** + * Data store containing all state retained for invocations of + * [rememberSession][com.android.systemui.scene.session.ui.composable.Session.rememberSession] + */ + val storage: MutableMap<String, StorageEntry> + get() = _storage + + /** + * Storage for an individual invocation of + * [rememberSession][com.android.systemui.scene.session.ui.composable.Session.rememberSession] + */ + class StorageEntry(val keys: Array<out Any?>, var stored: Any?) + + /** Clears the data store; any downstream usage within `@Composable`s will be recomposed. */ + fun clear() { + _storage = hashMapOf() + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt new file mode 100644 index 000000000000..924aa540aa7f --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.session.ui.composable + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.currentCompositeKeyHash +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.SaverScope +import androidx.compose.runtime.saveable.mapSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.android.systemui.scene.session.shared.SessionStorage +import com.android.systemui.util.kotlin.mapValuesNotNullTo + +/** + * An explicit storage for remembering composable state outside of the lifetime of a composition. + * + * Specifically, this allows easy conversion of standard + * [remember][androidx.compose.runtime.remember] invocations to ones that are preserved beyond the + * callsite's existence in the composition. + * + * ```kotlin + * @Composable + * fun Parent() { + * val session = remember { Session() } + * ... + * if (someCondition) { + * Child(session) + * } + * } + * + * @Composable + * fun Child(session: Session) { + * val state by session.rememberSession { mutableStateOf(0f) } + * ... + * } + * ``` + */ +interface Session { + /** + * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had + * in the previous composition, otherwise produce and remember a new value by calling [init]. + * + * @param inputs A set of inputs such that, when any of them have changed, will cause the state + * to reset and [init] to be rerun + * @param key An optional key to be used as a key for the saved value. If `null`, we use the one + * automatically generated by the Compose runtime which is unique for the every exact code + * location in the composition tree + * @param init A factory function to create the initial value of this state + * @see androidx.compose.runtime.remember + */ + @Composable fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T +} + +/** Returns a new [Session], optionally backed by the provided [SessionStorage]. */ +fun Session(storage: SessionStorage = SessionStorage()): Session = SessionImpl(storage) + +/** + * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had in + * the previous composition, otherwise produce and remember a new value by calling [init]. + * + * @param inputs A set of inputs such that, when any of them have changed, will cause the state to + * reset and [init] to be rerun + * @param key An optional key to be used as a key for the saved value. If not provided we use the + * one automatically generated by the Compose runtime which is unique for the every exact code + * location in the composition tree + * @param init A factory function to create the initial value of this state + * @see androidx.compose.runtime.remember + */ +@Composable +fun <T> Session.rememberSession(vararg inputs: Any?, key: String? = null, init: () -> T): T = + rememberSession(key, inputs, init = init) + +/** + * An explicit storage for remembering composable state outside of the lifetime of a composition. + * + * Specifically, this allows easy conversion of standard [rememberSession] invocations to ones that + * are preserved beyond the callsite's existence in the composition. + * + * ```kotlin + * @Composable + * fun Parent() { + * val session = rememberSaveableSession() + * ... + * if (someCondition) { + * Child(session) + * } + * } + * + * @Composable + * fun Child(session: SaveableSession) { + * val state by session.rememberSaveableSession { mutableStateOf(0f) } + * ... + * } + * ``` + */ +interface SaveableSession : Session { + /** + * Remember the value produced by [init]. + * + * It behaves similarly to [rememberSession], but the stored value will survive the activity or + * process recreation using the saved instance state mechanism (for example it happens when the + * screen is rotated in the Android application). + * + * @param inputs A set of inputs such that, when any of them have changed, will cause the state + * to reset and [init] to be rerun + * @param saver The [Saver] object which defines how the state is saved and restored. + * @param key An optional key to be used as a key for the saved value. If not provided we use + * the automatically generated by the Compose runtime which is unique for the every exact code + * location in the composition tree + * @param init A factory function to create the initial value of this state + * @see rememberSaveable + */ + @Composable + fun <T : Any> rememberSaveableSession( + vararg inputs: Any?, + saver: Saver<T, out Any>, + key: String?, + init: () -> T, + ): T +} + +/** + * Returns a new [SaveableSession] that is preserved across configuration changes. + * + * @param inputs A set of inputs such that, when any of them have changed, will cause the state to + * reset. + * @param key An optional key to be used as a key for the saved value. If not provided we use the + * automatically generated by the Compose runtime which is unique for the every exact code + * location in the composition tree. + */ +@Composable +fun rememberSaveableSession( + vararg inputs: Any?, + key: String? = null, +): SaveableSession = + rememberSaveable(inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() } + +private class SessionImpl( + private val storage: SessionStorage = SessionStorage(), +) : Session { + @Composable + override fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T { + val storage = storage.storage + val compositeKey = currentCompositeKeyHash + // key is the one provided by the user or the one generated by the compose runtime + val finalKey = + if (!key.isNullOrEmpty()) { + key + } else { + compositeKey.toString(MAX_SUPPORTED_RADIX) + } + if (finalKey !in storage) { + val value = init() + SideEffect { storage[finalKey] = SessionStorage.StorageEntry(inputs, value) } + return value + } + val entry = storage[finalKey]!! + if (!inputs.contentEquals(entry.keys)) { + val value = init() + SideEffect { entry.stored = value } + return value + } + @Suppress("UNCHECKED_CAST") return entry.stored as T + } +} + +private class SaveableSessionImpl( + saveableStorage: MutableMap<String, StorageEntry> = mutableMapOf(), + sessionStorage: SessionStorage = SessionStorage(), +) : SaveableSession, Session by Session(sessionStorage) { + + var saveableStorage: MutableMap<String, StorageEntry> by mutableStateOf(saveableStorage) + + @Composable + override fun <T : Any> rememberSaveableSession( + vararg inputs: Any?, + saver: Saver<T, out Any>, + key: String?, + init: () -> T, + ): T { + val compositeKey = currentCompositeKeyHash + // key is the one provided by the user or the one generated by the compose runtime + val finalKey = + if (!key.isNullOrEmpty()) { + key + } else { + compositeKey.toString(MAX_SUPPORTED_RADIX) + } + + @Suppress("UNCHECKED_CAST") (saver as Saver<T, Any>) + + if (finalKey !in saveableStorage) { + val value = init() + SideEffect { saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver) } + return value + } + when (val entry = saveableStorage[finalKey]!!) { + is StorageEntry.Unrestored -> { + val value = saver.restore(entry.unrestored) ?: init() + SideEffect { + saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver) + } + return value + } + is StorageEntry.Restored<*> -> { + if (!inputs.contentEquals(entry.inputs)) { + val value = init() + SideEffect { + saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver) + } + return value + } + @Suppress("UNCHECKED_CAST") return entry.stored as T + } + } + } + + sealed class StorageEntry { + class Unrestored(val unrestored: Any) : StorageEntry() + + class Restored<T>(val inputs: Array<out Any?>, var stored: T, val saver: Saver<T, Any>) : + StorageEntry() { + fun SaverScope.saveEntry() { + with(saver) { stored?.let { save(it) } } + } + } + } + + object SessionSaver : + Saver<SaveableSessionImpl, Any> by mapSaver( + save = { sessionScope: SaveableSessionImpl -> + sessionScope.saveableStorage.mapValues { (k, v) -> + when (v) { + is StorageEntry.Unrestored -> v.unrestored + is StorageEntry.Restored<*> -> { + with(v) { saveEntry() } + } + } + } + }, + restore = { savedMap: Map<String, Any?> -> + SaveableSessionImpl( + saveableStorage = + savedMap.mapValuesNotNullTo(mutableMapOf()) { (k, v) -> + v?.let { StorageEntry.Unrestored(v) } + } + ) + } + ) +} + +private const val MAX_SUPPORTED_RADIX = 36 diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt index 7af9b7bb90e9..92b2b4e886b9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -34,6 +33,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.motionEventSpy import androidx.compose.ui.input.pointer.pointerInput +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.MutableSceneTransitionLayoutState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.SceneTransitionLayout @@ -68,8 +68,9 @@ fun SceneContainer( modifier: Modifier = Modifier, ) { val coroutineScope = rememberCoroutineScope() - val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState() - val currentDestinations by viewModel.currentDestinationScenes(coroutineScope).collectAsState() + val currentSceneKey: SceneKey by viewModel.currentScene.collectAsStateWithLifecycle() + val currentDestinations by + viewModel.currentDestinationScenes(coroutineScope).collectAsStateWithLifecycle() val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( initialScene = currentSceneKey, 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 19c5800c7559..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 @@ -5,16 +5,18 @@ import com.android.compose.animation.scene.transitions 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.CollapseShadeInstantly 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 @@ -39,9 +41,9 @@ val SceneContainerTransitions = transitions { from( Scenes.Gone, to = Scenes.Shade, - key = CollapseShadeInstantly, + key = ToSplitShade, ) { - goneToShadeTransition(durationScale = 0.0) + goneToSplitShadeTransition() } from( Scenes.Gone, @@ -57,9 +59,9 @@ val SceneContainerTransitions = transitions { from( Scenes.Lockscreen, to = Scenes.Shade, - key = CollapseShadeInstantly, + key = ToSplitShade, ) { - lockscreenToShadeTransition(durationScale = 0.0) + lockscreenToSplitShadeTransition() } from( Scenes.Lockscreen, @@ -83,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/SceneTransitionLayoutDataSource.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt index a54994df3dc9..4b4b7ed33458 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneTransitionLayoutDataSource.kt @@ -19,7 +19,6 @@ package com.android.systemui.scene.ui.composable import com.android.compose.animation.scene.MutableSceneTransitionLayoutState -import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey import com.android.compose.animation.scene.observableTransitionState @@ -29,8 +28,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** @@ -48,19 +45,7 @@ class SceneTransitionLayoutDataSource( override val currentScene: StateFlow<SceneKey> = state .observableTransitionState() - .flatMapLatest { observableTransitionState -> - when (observableTransitionState) { - is ObservableTransitionState.Idle -> flowOf(observableTransitionState.scene) - is ObservableTransitionState.Transition -> - observableTransitionState.isUserInputOngoing.map { isUserInputOngoing -> - if (isUserInputOngoing) { - observableTransitionState.fromScene - } else { - observableTransitionState.toScene - } - } - } - } + .flatMapLatest { it.currentScene() } .stateIn( scope = coroutineScope, started = SharingStarted.WhileSubscribed(), @@ -77,4 +62,10 @@ class SceneTransitionLayoutDataSource( coroutineScope = coroutineScope, ) } + + override fun snapToScene(toScene: SceneKey) { + state.snapToScene( + scene = toScene, + ) + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt index 851719d387f5..918e1ad795c4 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt @@ -1,14 +1,12 @@ package com.android.systemui.scene.ui.composable.transitions -import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.goneToQuickSettingsTransition( durationScale: Double = 1.0, ) { - spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) - toQuickSettingsTransition() + toQuickSettingsTransition(durationScale = durationScale) } private val DefaultDuration = 500.milliseconds diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt index a0f410ab27fb..d3ef27372435 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt @@ -1,14 +1,9 @@ package com.android.systemui.scene.ui.composable.transitions -import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder -import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.goneToShadeTransition( durationScale: Double = 1.0, ) { - spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) - toShadeTransition() + toShadeTransition(durationScale = durationScale) } - -private val DefaultDuration = 500.milliseconds 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/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt index 319438c256dd..ba30a859f4e0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt @@ -1,14 +1,9 @@ package com.android.systemui.scene.ui.composable.transitions -import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder -import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.lockscreenToQuickSettingsTransition( durationScale: Double = 1.0, ) { - spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) - toQuickSettingsTransition() + toQuickSettingsTransition(durationScale = durationScale) } - -private val DefaultDuration = 500.milliseconds diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt index f078b8c9b78b..2aded0db3c37 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt @@ -1,14 +1,9 @@ package com.android.systemui.scene.ui.composable.transitions -import androidx.compose.animation.core.tween import com.android.compose.animation.scene.TransitionBuilder -import kotlin.time.Duration.Companion.milliseconds fun TransitionBuilder.lockscreenToShadeTransition( durationScale: Double = 1.0, ) { - spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt()) - toShadeTransition() + toShadeTransition(durationScale = durationScale) } - -private val DefaultDuration = 500.milliseconds 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/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt new file mode 100644 index 000000000000..00ef11d3b745 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.ui.composable + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.scene.ElementKey +import com.android.compose.animation.scene.LowestZIndexScenePicker +import com.android.compose.animation.scene.SceneScope +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel + +/** The overlay shade renders a lightweight shade UI container on top of a background scene. */ +@Composable +fun SceneScope.OverlayShade( + viewModel: OverlayShadeViewModel, + horizontalArrangement: Arrangement.Horizontal, + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle() + + Box(modifier) { + if (backgroundScene == Scenes.Lockscreen) { + Lockscreen() + } + + Scrim(onClicked = viewModel::onScrimClicked) + + Row( + modifier = Modifier.fillMaxSize().padding(OverlayShade.Dimensions.ScrimContentPadding), + horizontalArrangement = horizontalArrangement, + ) { + Panel(content = content) + } + } +} + +@Composable +private fun Lockscreen( + modifier: Modifier = Modifier, +) { + // TODO(b/338025605): This is a placeholder, replace with the actual lockscreen. + Box(modifier = modifier.fillMaxSize().background(Color.LightGray)) { + Text(text = "Lockscreen", modifier = Modifier.align(Alignment.Center)) + } +} + +@Composable +private fun SceneScope.Scrim( + onClicked: () -> Unit, + modifier: Modifier = Modifier, +) { + Spacer( + modifier = + modifier + .element(OverlayShade.Elements.Scrim) + .fillMaxSize() + .background(OverlayShade.Colors.ScrimBackground) + .clickable(onClick = onClicked, interactionSource = null, indication = null) + ) +} + +@Composable +private fun SceneScope.Panel( + modifier: Modifier = Modifier, + content: @Composable () -> Unit, +) { + Box( + modifier = + modifier + .width(OverlayShade.Dimensions.PanelWidth) + .clip(OverlayShade.Shapes.RoundedCornerPanel) + ) { + Spacer( + modifier = + Modifier.element(OverlayShade.Elements.PanelBackground) + .matchParentSize() + .background( + color = OverlayShade.Colors.PanelBackground, + shape = OverlayShade.Shapes.RoundedCornerPanel, + ), + ) + + // This content is intentionally rendered as a separate element from the background in order + // to allow for more flexibility when defining transitions. + content() + } +} + +object OverlayShade { + object Elements { + val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker) + val PanelBackground = + ElementKey("OverlayShadePanelBackground", scenePicker = LowestZIndexScenePicker) + } + + object Colors { + val ScrimBackground = Color(0, 0, 0, alpha = 255 / 3) + val PanelBackground: Color + @Composable @ReadOnlyComposable get() = MaterialTheme.colorScheme.surfaceContainer + } + + object Dimensions { + val ScrimContentPadding = 16.dp + val PanelCornerRadius = 46.dp + // TODO(b/338033836): This width should not be fixed. + val PanelWidth = 390.dp + } + + object Shapes { + val RoundedCornerPanel = RoundedCornerShape(Dimensions.PanelCornerRadius) + } +} 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 d3b3d1585f83..ac3e015e52a9 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 @@ -35,7 +35,6 @@ import androidx.compose.foundation.layout.widthIn import androidx.compose.material3.ColorScheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember @@ -50,7 +49,9 @@ 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 androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.SceneScope @@ -63,6 +64,8 @@ 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 import com.android.systemui.scene.shared.model.Scenes @@ -76,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 { @@ -100,6 +104,10 @@ object ShadeHeader { val ColorScheme.shadeHeaderText: Color get() = Color.White } + + object TestTags { + const val Root = "shade_header_root" + } } @Composable @@ -110,13 +118,17 @@ fun SceneScope.CollapsedShadeHeader( statusBarIconController: StatusBarIconController, modifier: Modifier = Modifier, ) { - val isDisabled by viewModel.isDisabled.collectAsState() + val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle() if (isDisabled) { return } 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) { @@ -126,16 +138,16 @@ fun SceneScope.CollapsedShadeHeader( } } - val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState() + val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() // This layout assumes it is globally positioned at (0, 0) and is the // same size as the screen. Layout( - modifier = modifier, + modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root), contents = listOf( { - Row { + Row(modifier = Modifier.padding(horizontal = horizontalPadding)) { Clock( scale = 1f, viewModel = viewModel, @@ -152,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), @@ -161,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 -> @@ -201,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] @@ -250,7 +271,7 @@ fun SceneScope.ExpandedShadeHeader( statusBarIconController: StatusBarIconController, modifier: Modifier = Modifier, ) { - val isDisabled by viewModel.isDisabled.collectAsState() + val isDisabled by viewModel.isDisabled.collectAsStateWithLifecycle() if (isDisabled) { return } @@ -259,9 +280,9 @@ fun SceneScope.ExpandedShadeHeader( derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } } - val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsState() + val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() - Box(modifier = modifier) { + Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) { if (isPrivacyChipVisible) { Box(modifier = Modifier.height(CollapsedHeight).fillMaxWidth()) { PrivacyChip( @@ -414,7 +435,7 @@ private fun ShadeCarrierGroup( modifier: Modifier = Modifier, ) { Row(modifier = modifier) { - val subIds by viewModel.mobileSubIds.collectAsState() + val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle() for (subId in subIds) { Spacer(modifier = Modifier.width(5.dp)) @@ -451,10 +472,12 @@ private fun SceneScope.StatusIcons( val micSlot = stringResource(id = com.android.internal.R.string.status_bar_microphone) val locationSlot = stringResource(id = com.android.internal.R.string.status_bar_location) - val isSingleCarrier by viewModel.isSingleCarrier.collectAsState() - val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsState() - val isMicCameraIndicationEnabled by viewModel.isMicCameraIndicationEnabled.collectAsState() - val isLocationIndicationEnabled by viewModel.isLocationIndicationEnabled.collectAsState() + val isSingleCarrier by viewModel.isSingleCarrier.collectAsStateWithLifecycle() + val isPrivacyChipEnabled by viewModel.isPrivacyChipEnabled.collectAsStateWithLifecycle() + val isMicCameraIndicationEnabled by + viewModel.isMicCameraIndicationEnabled.collectAsStateWithLifecycle() + val isLocationIndicationEnabled by + viewModel.isLocationIndicationEnabled.collectAsStateWithLifecycle() AndroidView( factory = { context -> @@ -523,7 +546,7 @@ private fun SceneScope.PrivacyChip( viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier, ) { - val privacyList by viewModel.privacyItems.collectAsState() + val privacyList by viewModel.privacyItems.collectAsStateWithLifecycle() AndroidView( factory = { context -> 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 3122b99f7e56..9d689fc25b23 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 @@ -17,7 +17,9 @@ package com.android.systemui.shade.ui.composable import android.view.ViewGroup +import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.clipScrollableContainer @@ -29,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 @@ -40,8 +43,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -54,6 +57,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexScenePicker import com.android.compose.animation.scene.SceneScope @@ -64,6 +68,10 @@ 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.compose.modifiers.sysuiResTag import com.android.systemui.dagger.SysUISingleton import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController @@ -76,13 +84,16 @@ import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibi import com.android.systemui.qs.ui.composable.BrightnessMirror import com.android.systemui.qs.ui.composable.QuickSettings 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.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 @@ -93,6 +104,7 @@ object Shade { val MediaCarousel = ElementKey("ShadeMediaCarousel") val BackgroundScrim = ElementKey("ShadeBackgroundScrim", scenePicker = LowestZIndexScenePicker) + val SplitShadeStartColumn = ElementKey("SplitShadeStartColumn") } object Dimensions { @@ -116,6 +128,8 @@ object Shade { 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, @@ -123,6 +137,7 @@ constructor( private val mediaCarouselController: MediaCarouselController, @Named(QUICK_QS_PANEL) private val mediaHost: MediaHost, ) : ComposableScene { + override val key = Scenes.Shade override val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = @@ -133,6 +148,7 @@ constructor( modifier: Modifier, ) = ShadeScene( + notificationStackScrollView.get(), viewModel = viewModel, createTintedIconManager = tintedIconManagerFactory::create, createBatteryMeterViewController = batteryMeterViewControllerFactory::create, @@ -140,6 +156,7 @@ constructor( mediaCarouselController = mediaCarouselController, mediaHost = mediaHost, modifier = modifier, + shadeSession = shadeSession, ) init { @@ -151,6 +168,7 @@ constructor( @Composable private fun SceneScope.ShadeScene( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -158,11 +176,13 @@ private fun SceneScope.ShadeScene( mediaCarouselController: MediaCarouselController, mediaHost: MediaHost, modifier: Modifier = Modifier, + shadeSession: SaveableSession, ) { - val shadeMode by viewModel.shadeMode.collectAsState() + val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle() when (shadeMode) { is ShadeMode.Single -> SingleShade( + notificationStackScrollView = notificationStackScrollView, viewModel = viewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, @@ -170,9 +190,11 @@ private fun SceneScope.ShadeScene( mediaCarouselController = mediaCarouselController, mediaHost = mediaHost, modifier = modifier, + shadeSession = shadeSession, ) is ShadeMode.Split -> SplitShade( + notificationStackScrollView = notificationStackScrollView, viewModel = viewModel, createTintedIconManager = createTintedIconManager, createBatteryMeterViewController = createBatteryMeterViewController, @@ -180,6 +202,7 @@ private fun SceneScope.ShadeScene( mediaCarouselController = mediaCarouselController, mediaHost = mediaHost, modifier = modifier, + shadeSession = shadeSession, ) is ShadeMode.Dual -> error("Dual shade is not yet implemented!") } @@ -187,6 +210,7 @@ private fun SceneScope.ShadeScene( @Composable private fun SceneScope.SingleShade( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -194,7 +218,10 @@ private fun SceneScope.SingleShade( mediaCarouselController: MediaCarouselController, mediaHost: MediaHost, modifier: Modifier = Modifier, + shadeSession: SaveableSession, ) { + val cutoutLocation = LocalDisplayCutout.current.location + val maxNotifScrimTop = remember { mutableStateOf(0f) } val tileSquishiness by animateSceneFloatAsState( @@ -202,7 +229,8 @@ private fun SceneScope.SingleShade( key = QuickSettings.SharedValues.TilesSquishiness, canOverflow = false ) - val isClickable by viewModel.isClickable.collectAsState() + val isClickable by viewModel.isClickable.collectAsStateWithLifecycle() + val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() val shouldPunchHoleBehindScrim = layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) || @@ -230,31 +258,33 @@ 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( viewModel.qsSceneAdapter, { viewModel.qsSceneAdapter.qqsHeight }, isSplitShade = false, - squishiness = tileSquishiness, + squishiness = { tileSquishiness }, ) } MediaCarousel( - isVisible = viewModel::isMediaVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, @@ -265,8 +295,11 @@ private fun SceneScope.SingleShade( }, { NotificationScrollingStack( + shadeSession = shadeSession, + stackScrollView = notificationStackScrollView, viewModel = viewModel.notifications, maxScrimTop = { maxNotifScrimTop.value }, + shadeMode = ShadeMode.Single, shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, ) }, @@ -291,6 +324,7 @@ private fun SceneScope.SingleShade( @Composable private fun SceneScope.SplitShade( + notificationStackScrollView: NotificationScrollView, viewModel: ShadeSceneViewModel, createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager, createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController, @@ -298,27 +332,44 @@ private fun SceneScope.SplitShade( mediaCarouselController: MediaCarouselController, mediaHost: MediaHost, modifier: Modifier = Modifier, + shadeSession: SaveableSession, ) { - val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsState() + val screenCornerRadius = LocalScreenCornerRadius.current + + val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle() + val isCustomizerShowing by + viewModel.qsSceneAdapter.isCustomizerShowing.collectAsStateWithLifecycle() + val customizingAnimationDuration by + viewModel.qsSceneAdapter.customizerAnimationDuration.collectAsStateWithLifecycle() val lifecycleOwner = LocalLifecycleOwner.current 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( isOnStartSide = true, ) - .collectAsState(0f) + .collectAsStateWithLifecycle(0f) val unfoldTranslationXForEndSide by viewModel .unfoldTranslationX( isOnStartSide = false, ) - .collectAsState(0f) + .collectAsStateWithLifecycle(0f) val navBarBottomHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + val bottomPadding by + animateDpAsState( + targetValue = if (isCustomizing) 0.dp else navBarBottomHeight, + animationSpec = tween(customizingAnimationDuration), + label = "animateQSSceneBottomPaddingAsState" + ) val density = LocalDensity.current LaunchedEffect(navBarBottomHeight, density) { with(density) { @@ -334,13 +385,19 @@ private fun SceneScope.SplitShade( } } - val brightnessMirrorShowing by viewModel.brightnessMirrorViewModel.isShowing.collectAsState() + val brightnessMirrorShowing by + viewModel.brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle() val contentAlpha by animateFloatAsState( targetValue = if (brightnessMirrorShowing) 0f else 1f, label = "alphaAnimationBrightnessMirrorContentHiding", ) + viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha) + DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } } + + val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle() + val brightnessMirrorShowingModifier = Modifier.graphicsLayer { alpha = contentAlpha } Box( @@ -363,8 +420,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() }, ) @@ -373,9 +429,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, @@ -386,16 +442,14 @@ private fun SceneScope.SplitShade( ) Column( verticalArrangement = Arrangement.Top, - modifier = - Modifier.fillMaxSize().thenIf(!isCustomizing) { - Modifier.padding(bottom = navBarBottomHeight) - }, + modifier = Modifier.fillMaxSize().padding(bottom = bottomPadding), ) { Column( modifier = Modifier.fillMaxSize() + .sysuiResTag("expanded_qs_scroll_view") .weight(1f) - .thenIf(!isCustomizing) { + .thenIf(!isCustomizerShowing) { Modifier.verticalNestedScrollToScene() .verticalScroll( quickSettingsScrollState, @@ -414,12 +468,12 @@ private fun SceneScope.SplitShade( heightProvider = { viewModel.qsSceneAdapter.qsHeight }, isSplitShade = true, modifier = Modifier.fillMaxWidth(), - squishiness = tileSquishiness, + squishiness = { tileSquishiness }, ) } MediaCarousel( - isVisible = viewModel::isMediaVisible, + isVisible = isMediaVisible, mediaHost = mediaHost, modifier = Modifier.fillMaxWidth(), carouselController = mediaCarouselController, @@ -428,22 +482,27 @@ private fun SceneScope.SplitShade( FooterActionsWithAnimatedVisibility( viewModel = footerActionsViewModel, isCustomizing = isCustomizing, + customizingAnimationDuration = customizingAnimationDuration, lifecycleOwner = lifecycleOwner, modifier = Modifier.align(Alignment.CenterHorizontally) + .sysuiResTag("qs_footer_actions") .then(brightnessMirrorShowingModifier), ) } } 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/shade/ui/composable/VariableDayDate.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt index 5e107c60bee6..931ff56e56cb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/VariableDayDate.kt @@ -3,9 +3,9 @@ package com.android.systemui.shade.ui.composable import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.layout.Layout +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.shade.ui.composable.ShadeHeader.Colors.shadeHeaderText import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel @@ -14,8 +14,8 @@ fun VariableDayDate( viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier, ) { - val longerText = viewModel.longerDateText.collectAsState() - val shorterText = viewModel.shorterDateText.collectAsState() + val longerText = viewModel.longerDateText.collectAsStateWithLifecycle() + val shorterText = viewModel.shorterDateText.collectAsStateWithLifecycle() Layout( contents = 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 00225fc3577a..3976c618c179 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,29 +16,40 @@ 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.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors import androidx.compose.material3.MaterialTheme 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.graphics.Color +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.LiveRegionMode import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription -import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.liveRegion import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle 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 @@ -52,32 +63,52 @@ constructor( @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { - val slice by viewModel.buttonSlice.collectAsState() + val slice by viewModel.buttonSlice.collectAsStateWithLifecycle() 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) Column( - modifier = modifier, + modifier = + modifier.onGloballyPositioned { + gravity = VolumePanelPopup.calculateGravity(it, screenWidth) + }, verticalArrangement = Arrangement.spacedBy(12.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - SliceAndroidView( - modifier = - Modifier.height(64.dp) - .fillMaxWidth() - .semantics { - role = Role.Button + Box( + modifier = Modifier.height(64.dp), + ) { + SliceAndroidView( + modifier = modifier.fillMaxSize(), + slice = slice, + onWidthChanged = viewModel::onButtonSliceWidthChanged, + enableAccessibility = false, + ) + Button( + modifier = + modifier.fillMaxSize().padding(8.dp).semantics { + liveRegion = LiveRegionMode.Polite contentDescription = label - } - .clip(RoundedCornerShape(28.dp)), - slice = slice, - onWidthChanged = viewModel::onButtonSliceWidthChanged, - onClick = { ancPopup.show(null) } - ) + }, + enabled = isClickable, + onClick = { with(ancPopup) { show(null, gravity) } }, + colors = + ButtonColors( + contentColor = Color.Transparent, + containerColor = Color.Transparent, + disabledContentColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + ) + ) {} + } + Text( - modifier = Modifier.clearAndSetSemantics {}, + modifier = Modifier.clearAndSetSemantics {}.basicMarquee(), text = label, style = MaterialTheme.typography.labelMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis, + maxLines = 2, ) } } 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..76ffc8b379ae 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,17 +16,18 @@ 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 import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.slice.Slice import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.Expandable @@ -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 @@ -65,14 +67,18 @@ constructor( @Composable private fun Content(dialog: SystemUIDialog) { - val isAvailable by viewModel.isAvailable.collectAsState(true) - + val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle(true) if (!isAvailable) { SideEffect { dialog.dismiss() } return } - val slice by viewModel.popupSlice.collectAsState() + val slice by viewModel.popupSlice.collectAsStateWithLifecycle() + if (!viewModel.isClickable(slice)) { + SideEffect { dialog.dismiss() } + return + } + SliceAndroidView( modifier = Modifier.fillMaxWidth(), slice = slice, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt index 74af3ca19266..23d50c577300 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/anc/ui/composable/SliceAndroidView.kt @@ -16,11 +16,12 @@ package com.android.systemui.volume.panel.component.anc.ui.composable -import android.annotation.SuppressLint import android.content.Context +import android.os.Bundle import android.view.ContextThemeWrapper -import android.view.MotionEvent import android.view.View +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityNodeInfo import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView @@ -33,29 +34,31 @@ fun SliceAndroidView( slice: Slice?, modifier: Modifier = Modifier, onWidthChanged: ((Int) -> Unit)? = null, - onClick: (() -> Unit)? = null, + enableAccessibility: Boolean = true, ) { AndroidView( modifier = modifier, factory = { context: Context -> - ClickableSliceView( + ComposeSliceView( ContextThemeWrapper(context, R.style.Widget_SliceView_VolumePanel), - onClick, ) .apply { mode = SliceView.MODE_LARGE isScrollable = false importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO setShowTitleItems(true) - if (onWidthChanged != null) { - addOnLayoutChangeListener(OnWidthChangedLayoutListener(onWidthChanged)) - } - if (onClick != null) { - setOnClickListener { onClick() } - } } }, - update = { sliceView: SliceView -> sliceView.slice = slice } + update = { sliceView: ComposeSliceView -> + sliceView.slice = slice + sliceView.layoutListener = onWidthChanged?.let(::OnWidthChangedLayoutListener) + sliceView.enableAccessibility = enableAccessibility + }, + onRelease = { sliceView: ComposeSliceView -> + sliceView.layoutListener = null + sliceView.slice = null + sliceView.enableAccessibility = true + }, ) } @@ -81,27 +84,39 @@ class OnWidthChangedLayoutListener(private val widthChanged: (Int) -> Unit) : } } -/** - * [SliceView] that prioritises [onClick] when its clicked instead of passing the event to the slice - * first. - */ -@SuppressLint("ViewConstructor") // only used in this class -private class ClickableSliceView( - context: Context, - private val onClick: (() -> Unit)?, -) : SliceView(context) { +private class ComposeSliceView(context: Context) : SliceView(context) { + + var enableAccessibility: Boolean = true + var layoutListener: OnLayoutChangeListener? = null + set(value) { + field?.let { removeOnLayoutChangeListener(it) } + field = value + field?.let { addOnLayoutChangeListener(it) } + } - init { - if (onClick != null) { - setOnClickListener {} + override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) { + if (enableAccessibility) { + super.onInitializeAccessibilityNodeInfo(info) } } - override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean { - return (isSliceViewClickable && onClick != null) || super.onInterceptTouchEvent(ev) + override fun onInitializeAccessibilityEvent(event: AccessibilityEvent?) { + if (enableAccessibility) { + super.onInitializeAccessibilityEvent(event) + } } - override fun onClick(v: View?) { - onClick?.takeIf { isSliceViewClickable }?.let { it() } ?: super.onClick(v) + override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean { + return if (enableAccessibility) { + super.performAccessibilityAction(action, arguments) + } else { + false + } + } + + override fun addChildrenForAccessibility(outChildren: ArrayList<View>?) { + if (enableAccessibility) { + super.addChildrenForAccessibility(outChildren) + } } } 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..e1ae80f13312 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 @@ -27,20 +28,27 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme 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 import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable import com.android.systemui.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,17 +56,22 @@ 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 override fun VolumePanelComposeScope.Content(modifier: Modifier) { - val viewModelByState by viewModelFlow.collectAsState() + val viewModelByState by viewModelFlow.collectAsStateWithLifecycle() 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/button/ui/composable/ToggleButtonComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt index 874c0a299d2b..1b821d36dceb 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/button/ui/composable/ToggleButtonComponent.kt @@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.button.ui.composable import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -28,7 +29,6 @@ import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -41,6 +41,7 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.common.ui.compose.Icon import com.android.systemui.volume.panel.component.button.ui.viewmodel.ButtonViewModel import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent @@ -55,7 +56,7 @@ class ToggleButtonComponent( @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { - val viewModelByState by viewModelFlow.collectAsState() + val viewModelByState by viewModelFlow.collectAsStateWithLifecycle() val viewModel = viewModelByState ?: return val label = viewModel.label.toString() @@ -91,7 +92,8 @@ class ToggleButtonComponent( }, onClick = { onCheckedChange(!viewModel.isActive) }, shape = RoundedCornerShape(28.dp), - colors = colors + colors = colors, + contentPadding = PaddingValues(0.dp) ) { Icon(modifier = Modifier.size(24.dp), icon = viewModel.icon) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt index 6f2ed8178801..237bbfd714b9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt @@ -45,7 +45,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -55,6 +54,7 @@ import androidx.compose.ui.semantics.liveRegion import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.toColor @@ -77,16 +77,19 @@ constructor( @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { val connectedDeviceViewModel: ConnectedDeviceViewModel? by - viewModel.connectedDeviceViewModel.collectAsState() + viewModel.connectedDeviceViewModel.collectAsStateWithLifecycle() val deviceIconViewModel: DeviceIconViewModel? by - viewModel.deviceIconViewModel.collectAsState() + viewModel.deviceIconViewModel.collectAsStateWithLifecycle() val clickLabel = stringResource(R.string.volume_panel_enter_media_output_settings) Expandable( modifier = Modifier.fillMaxWidth().height(80.dp).semantics { liveRegion = LiveRegionMode.Polite - this.onClick(label = clickLabel) { false } + this.onClick(label = clickLabel) { + viewModel.onBarClick(null) + true + } }, color = MaterialTheme.colorScheme.surface, shape = RoundedCornerShape(28.dp), 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..9891b5b5eba2 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,17 +16,18 @@ 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 import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.Expandable import com.android.systemui.common.ui.compose.Icon @@ -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 @@ -70,14 +72,14 @@ constructor( @Composable private fun Content(dialog: SystemUIDialog) { - val isAvailable by viewModel.isAvailable.collectAsState() + val isAvailable by viewModel.isAvailable.collectAsStateWithLifecycle() if (!isAvailable) { SideEffect { dialog.dismiss() } return } - val enabledModelStates by viewModel.spatialAudioButtons.collectAsState() + val enabledModelStates by viewModel.spatialAudioButtons.collectAsStateWithLifecycle() if (enabledModelStates.isEmpty()) { return } 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..072e91a25444 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,7 +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.collectAsState +import androidx.compose.runtime.State import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -49,15 +49,22 @@ 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 androidx.lifecycle.compose.collectAsStateWithLifecycle 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 sliderState by viewModels.first().slider.collectAsStateWithLifecycle() + 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 }, @@ -119,7 +119,7 @@ fun ColumnVolumeSliders( Column { for (index in 1..viewModels.lastIndex) { val sliderViewModel: SliderViewModel = viewModels[index] - val sliderState by sliderViewModel.slider.collectAsState() + val sliderState by sliderViewModel.slider.collectAsStateWithLifecycle() transition.AnimatedVisibility( modifier = Modifier.padding(top = 16.dp), 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/GridVolumeSliders.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt index bb17499f021f..d15430faa0a0 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/GridVolumeSliders.kt @@ -18,9 +18,9 @@ package com.android.systemui.volume.panel.component.volume.ui.composable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSliderColors import com.android.compose.grid.VerticalGrid import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel @@ -39,7 +39,7 @@ fun GridVolumeSliders( horizontalSpacing = 24.dp, ) { for (sliderViewModel in viewModels) { - val sliderState = sliderViewModel.slider.collectAsState().value + val sliderState = sliderViewModel.slider.collectAsStateWithLifecycle().value VolumeSlider( modifier = Modifier.fillMaxWidth(), state = sliderState, 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 9f5ab3c0e284..271eb9601dbd 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,13 +63,27 @@ fun VolumeSlider( PlatformSlider( modifier = modifier.clearAndSetSemantics { - if (!state.isEnabled) disabled() - contentDescription = - state.disabledMessage?.let { "${state.label}, $it" } ?: state.label + if (state.isEnabled) { + contentDescription = state.label + state.a11yClickDescription?.let { + customActions = + listOf( + CustomAccessibilityAction( + it, + ) { + onIconTapped() + true + } + ) + } - // 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) + state.a11yStateDescription?.let { stateDescription = it } + progressBarRangeInfo = ProgressBarRangeInfo(state.value, state.valueRange) + } else { + disabled() + contentDescription = + state.disabledMessage?.let { "${state.label}, $it" } ?: state.label + } setProgress { targetValue -> val targetDirection = when { 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..770c5d5c247c 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 @@ -18,12 +18,13 @@ package com.android.systemui.volume.panel.component.volume.ui.composable 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 androidx.lifecycle.compose.collectAsStateWithLifecycle 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 @@ -37,7 +38,8 @@ constructor( @Composable override fun VolumePanelComposeScope.Content(modifier: Modifier) { - val sliderViewModels: List<SliderViewModel> by viewModel.sliderViewModels.collectAsState() + val sliderViewModels: List<SliderViewModel> by + viewModel.sliderViewModels.collectAsStateWithLifecycle() if (sliderViewModels.isEmpty()) { return } @@ -48,13 +50,21 @@ constructor( modifier = modifier.fillMaxWidth(), ) } else { - val isExpanded by viewModel.isExpanded.collectAsState() + val expandableViewModel: SlidersExpandableViewModel by + viewModel + .isExpandable(isPortrait) + .collectAsStateWithLifecycle(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/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt index c51e8b0ead12..83b8158a9bcf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/VolumePanelRoot.kt @@ -24,11 +24,15 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.paneTitle +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.res.R import com.android.systemui.volume.panel.ui.layout.ComponentsLayout import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelState import com.android.systemui.volume.panel.ui.viewmodel.VolumePanelViewModel @@ -49,19 +53,22 @@ fun VolumePanelRoot( } } - val state: VolumePanelState by viewModel.volumePanelState.collectAsState() - val components by viewModel.componentsLayout.collectAsState(null) + val accessibilityTitle = stringResource(R.string.accessibility_volume_settings) + val state: VolumePanelState by viewModel.volumePanelState.collectAsStateWithLifecycle() + val components by viewModel.componentsLayout.collectAsStateWithLifecycle(null) with(VolumePanelComposeScope(state)) { components?.let { componentsState -> Components( componentsState, - modifier.padding( - start = padding, - top = padding, - end = padding, - bottom = 20.dp, - ) + modifier + .semantics { paneTitle = accessibilityTitle } + .padding( + start = padding, + top = padding, + end = padding, + bottom = 20.dp, + ) ) } } diff --git a/packages/SystemUI/compose/scene/OWNERS b/packages/SystemUI/compose/scene/OWNERS index 33a59c2bcab3..dac37eeb3e8c 100644 --- a/packages/SystemUI/compose/scene/OWNERS +++ b/packages/SystemUI/compose/scene/OWNERS @@ -2,12 +2,13 @@ set noparent # Bug component: 1184816 +amiko@google.com jdemeulenaere@google.com omarmt@google.com # SysUI Dr No's. # Don't send reviews here. -dsandler@android.com cinek@google.com +dsandler@android.com juliacr@google.com pixel@google.com
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt index 6b289f3c66a3..48a348b9d1c5 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt @@ -21,6 +21,7 @@ import androidx.compose.animation.core.AnimationVector1D import androidx.compose.animation.core.SpringSpec import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -47,8 +48,11 @@ internal fun CoroutineScope.animateToScene( } return when (transitionState) { - is TransitionState.Idle -> animate(layoutState, target, transitionKey) + is TransitionState.Idle -> + animate(layoutState, target, transitionKey, isInitiatedByUserInput = false) is TransitionState.Transition -> { + val isInitiatedByUserInput = transitionState.isInitiatedByUserInput + // A transition is currently running: first check whether `transition.toScene` or // `transition.fromScene` is the same as our target scene, in which case the transition // can be accelerated or reversed to end up in the target state. @@ -68,8 +72,14 @@ internal fun CoroutineScope.animateToScene( } else { // The transition is in progress: start the canned animation at the same // progress as it was in. - // TODO(b/290184746): Also take the current velocity into account. - animate(layoutState, target, transitionKey, startProgress = progress) + animate( + layoutState, + target, + transitionKey, + isInitiatedByUserInput, + initialProgress = progress, + initialVelocity = transitionState.progressVelocity, + ) } } else if (transitionState.fromScene == target) { // There is a transition from [target] to another scene: simply animate the same @@ -83,19 +93,52 @@ internal fun CoroutineScope.animateToScene( layoutState.finishTransition(transitionState, target) null } else { - // TODO(b/290184746): Also take the current velocity into account. animate( layoutState, target, transitionKey, - startProgress = progress, + isInitiatedByUserInput, + initialProgress = progress, + initialVelocity = transitionState.progressVelocity, reversed = true, ) } } else { // Generic interruption; the current transition is neither from or to [target]. - // TODO(b/290930950): Better handle interruptions here. - animate(layoutState, target, transitionKey) + val interruptionResult = + layoutState.transitions.interruptionHandler.onInterruption( + transitionState, + target, + ) + ?: DefaultInterruptionHandler.onInterruption(transitionState, target) + + val animateFrom = interruptionResult.animateFrom + if ( + animateFrom != transitionState.toScene && + animateFrom != transitionState.fromScene + ) { + error( + "InterruptionResult.animateFrom must be either the fromScene " + + "(${transitionState.fromScene.debugName}) or the toScene " + + "(${transitionState.toScene.debugName}) of the interrupted transition." + ) + } + + // If we were A => B and that we are now animating A => C, add a transition B => A + // to the list of transitions so that B "disappears back to A". + val chain = interruptionResult.chain + if (chain && animateFrom != transitionState.currentScene) { + animateToScene(layoutState, animateFrom, transitionKey = null) + } + + animate( + layoutState, + target, + transitionKey, + isInitiatedByUserInput, + fromScene = animateFrom, + chain = chain, + ) } } } @@ -103,32 +146,31 @@ internal fun CoroutineScope.animateToScene( private fun CoroutineScope.animate( layoutState: BaseSceneTransitionLayoutState, - target: SceneKey, + targetScene: SceneKey, transitionKey: TransitionKey?, - startProgress: Float = 0f, + isInitiatedByUserInput: Boolean, + initialProgress: Float = 0f, + initialVelocity: Float = 0f, reversed: Boolean = false, + fromScene: SceneKey = layoutState.transitionState.currentScene, + chain: Boolean = true, ): TransitionState.Transition { - val fromScene = layoutState.transitionState.currentScene - val isUserInput = - (layoutState.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput - ?: false - val targetProgress = if (reversed) 0f else 1f val transition = if (reversed) { OneOffTransition( - fromScene = target, + fromScene = targetScene, toScene = fromScene, - currentScene = target, - isInitiatedByUserInput = isUserInput, + currentScene = targetScene, + isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = false, ) } else { OneOffTransition( fromScene = fromScene, - toScene = target, - currentScene = target, - isInitiatedByUserInput = isUserInput, + toScene = targetScene, + currentScene = targetScene, + isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = false, ) } @@ -136,7 +178,7 @@ private fun CoroutineScope.animate( // Change the current layout state to start this new transition. This will compute the // TransformationSpec associated to this transition, which we need to initialize the Animatable // that will actually animate it. - layoutState.startTransition(transition, transitionKey) + layoutState.startTransition(transition, transitionKey, chain) // The transition now contains the transformation spec that we should use to instantiate the // Animatable. @@ -144,21 +186,22 @@ private fun CoroutineScope.animate( val visibilityThreshold = (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold val animatable = - Animatable(startProgress, visibilityThreshold = visibilityThreshold).also { + Animatable(initialProgress, visibilityThreshold = visibilityThreshold).also { transition.animatable = it } // Animate the progress to its target value. + // Important: We start atomically to make sure that we start the coroutine even if it is + // cancelled right after it is launched, so that finishTransition() is correctly called. + // Otherwise, this transition will never be stopped and we will never settle to Idle. transition.job = - launch { animatable.animateTo(targetProgress, animationSpec) } - .apply { - invokeOnCompletion { - // Settle the state to Idle(target). Note that this will do nothing if this - // transition was replaced/interrupted by another one, and this also runs if - // this coroutine is cancelled, i.e. if [this] coroutine scope is cancelled. - layoutState.finishTransition(transition, target) - } + launch(start = CoroutineStart.ATOMIC) { + try { + animatable.animateTo(targetProgress, animationSpec, initialVelocity) + } finally { + layoutState.finishTransition(transition, targetScene) } + } return transition } @@ -185,6 +228,9 @@ private class OneOffTransition( override val progress: Float get() = animatable.value + override val progressVelocity: Float + get() = animatable.velocity + override fun finish(): Job = job } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index f78ed2fdcaf6..1f812453915a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -33,6 +33,7 @@ import com.android.compose.animation.scene.TransitionState.HasOverscrollProperti import com.android.compose.nestedscroll.PriorityNestedScrollConnection import kotlin.math.absoluteValue import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -579,6 +580,18 @@ private class SwipeTransition( return offset / distance } + override val progressVelocity: Float + get() { + val animatable = offsetAnimation?.animatable ?: return 0f + val distance = distance() + if (distance == DistanceUnspecified) { + return 0f + } + + val velocityInDistanceUnit = animatable.velocity + return velocityInDistanceUnit / distance.absoluteValue + } + override val isInitiatedByUserInput = true override var bouncingScene: SceneKey? = null @@ -672,7 +685,11 @@ private class SwipeTransition( val isTargetGreater = targetOffset > animatable.value val job = coroutineScope - .launch { + // Important: We start atomically to make sure that we start the coroutine even + // if it is cancelled right after it is launched, so that snapToScene() is + // correctly called. Otherwise, this transition will never be stopped and we + // will never settle to Idle. + .launch(start = CoroutineStart.ATOMIC) { // TODO(b/327249191): Refactor the code so that we don't even launch a // coroutine if we don't need to animate. if (skipAnimation) { @@ -714,18 +731,15 @@ private class SwipeTransition( } } finally { bouncingScene = null + snapToScene(targetScene) } } - // Make sure that we settle to target scene at the end of the animation or if - // the animation is cancelled. - .apply { invokeOnCompletion { snapToScene(targetScene) } } OffsetAnimation(animatable, job) } } fun snapToScene(scene: SceneKey) { - if (layoutState.transitionState != this) return cancelOffsetAnimation() layoutState.finishTransition(this, idleScene = scene) } @@ -865,6 +879,7 @@ internal class NestedScrollHandlerImpl( private val orientation: Orientation, private val topOrLeftBehavior: NestedScrollBehavior, private val bottomOrRightBehavior: NestedScrollBehavior, + private val isExternalOverscrollGesture: () -> Boolean, ) { private val layoutState = layoutImpl.state private val draggableHandler = layoutImpl.draggableHandler(orientation) @@ -920,7 +935,8 @@ internal class NestedScrollHandlerImpl( return PriorityNestedScrollConnection( orientation = orientation, canStartPreScroll = { offsetAvailable, offsetBeforeStart -> - canChangeScene = offsetBeforeStart == 0f + canChangeScene = + if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f val canInterceptSwipeTransition = canChangeScene && @@ -950,7 +966,8 @@ internal class NestedScrollHandlerImpl( else -> return@PriorityNestedScrollConnection false } - val isZeroOffset = offsetBeforeStart == 0f + val isZeroOffset = + if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f val canStart = when (behavior) { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index ca643231e874..18baee995f9e 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -49,6 +49,7 @@ import androidx.compose.ui.util.lerp import com.android.compose.animation.scene.transformation.PropertyTransformation import com.android.compose.animation.scene.transformation.SharedElementTransformation import com.android.compose.ui.util.lerp +import kotlin.math.roundToInt import kotlinx.coroutines.launch /** An element on screen, that can be composed in one or more scenes. */ @@ -81,11 +82,13 @@ internal class Element(val key: ElementKey) { /** The last state this element had in this scene. */ var lastOffset = Offset.Unspecified + var lastSize = SizeUnspecified var lastScale = Scale.Unspecified var lastAlpha = AlphaUnspecified /** The state of this element in this scene right before the last interruption (if any). */ var offsetBeforeInterruption = Offset.Unspecified + var sizeBeforeInterruption = SizeUnspecified var scaleBeforeInterruption = Scale.Unspecified var alphaBeforeInterruption = AlphaUnspecified @@ -96,6 +99,7 @@ internal class Element(val key: ElementKey) { * they nicely animate from their values down to 0. */ var offsetInterruptionDelta = Offset.Zero + var sizeInterruptionDelta = IntSize.Zero var scaleInterruptionDelta = Scale.Zero var alphaInterruptionDelta = 0f @@ -127,7 +131,14 @@ internal fun Modifier.element( layoutImpl: SceneTransitionLayoutImpl, scene: Scene, key: ElementKey, -): Modifier = this.then(ElementModifier(layoutImpl, scene, key)).testTag(key.testTag) +): Modifier { + // Make sure that we read the current transitions during composition and not during + // layout/drawing. + // TODO(b/341072461): Revert this and read the current transitions in ElementNode directly once + // we can ensure that SceneTransitionLayoutImpl will compose new scenes first. + val currentTransitions = layoutImpl.state.currentTransitions + return then(ElementModifier(layoutImpl, currentTransitions, scene, key)).testTag(key.testTag) +} /** * An element associated to [ElementNode]. Note that this element does not support updates as its @@ -135,18 +146,20 @@ internal fun Modifier.element( */ private data class ElementModifier( private val layoutImpl: SceneTransitionLayoutImpl, + private val currentTransitions: List<TransitionState.Transition>, private val scene: Scene, private val key: ElementKey, ) : ModifierNodeElement<ElementNode>() { - override fun create(): ElementNode = ElementNode(layoutImpl, scene, key) + override fun create(): ElementNode = ElementNode(layoutImpl, currentTransitions, scene, key) override fun update(node: ElementNode) { - node.update(layoutImpl, scene, key) + node.update(layoutImpl, currentTransitions, scene, key) } } internal class ElementNode( private var layoutImpl: SceneTransitionLayoutImpl, + private var currentTransitions: List<TransitionState.Transition>, private var scene: Scene, private var key: ElementKey, ) : Modifier.Node(), DrawModifierNode, ApproachLayoutModifierNode { @@ -202,10 +215,13 @@ internal class ElementNode( fun update( layoutImpl: SceneTransitionLayoutImpl, + currentTransitions: List<TransitionState.Transition>, scene: Scene, key: ElementKey, ) { check(layoutImpl == this.layoutImpl && scene == this.scene) + this.currentTransitions = currentTransitions + removeNodeFromSceneState() val prevElement = this.element @@ -236,7 +252,7 @@ internal class ElementNode( measurable: Measurable, constraints: Constraints, ): MeasureResult { - val transitions = layoutImpl.state.currentTransitions + val transitions = currentTransitions val transition = elementTransition(element, transitions) // If this element is not supposed to be laid out now, either because it is not part of any @@ -251,11 +267,13 @@ internal class ElementNode( sceneState.lastAlpha = Element.AlphaUnspecified val placeable = measurable.measure(constraints) + sceneState.lastSize = placeable.size() return layout(placeable.width, placeable.height) {} } val placeable = measure(layoutImpl, scene, element, transition, sceneState, measurable, constraints) + sceneState.lastSize = placeable.size() return layout(placeable.width, placeable.height) { place( layoutImpl, @@ -270,7 +288,7 @@ internal class ElementNode( } override fun ContentDrawScope.draw() { - val transition = elementTransition(element, layoutImpl.state.currentTransitions) + val transition = elementTransition(element, currentTransitions) val drawScale = getDrawScale(layoutImpl, scene, element, transition, sceneState) if (drawScale == Scale.Default) { drawContent() @@ -329,10 +347,9 @@ private fun elementTransition( if (transition == null && previousTransition != null) { // The transition was just finished. - element.sceneStates.values.forEach { sceneState -> - sceneState.offsetInterruptionDelta = Offset.Zero - sceneState.scaleInterruptionDelta = Scale.Zero - sceneState.alphaInterruptionDelta = 0f + element.sceneStates.values.forEach { + it.clearValuesBeforeInterruption() + it.clearInterruptionDeltas() } } @@ -366,21 +383,34 @@ private fun prepareInterruption(element: Element) { } val lastOffset = lastUniqueState?.lastOffset ?: Offset.Unspecified + val lastSize = lastUniqueState?.lastSize ?: Element.SizeUnspecified val lastScale = lastUniqueState?.lastScale ?: Scale.Unspecified val lastAlpha = lastUniqueState?.lastAlpha ?: Element.AlphaUnspecified // Store the state of the element before the interruption and reset the deltas. sceneStates.forEach { sceneState -> sceneState.offsetBeforeInterruption = lastOffset + sceneState.sizeBeforeInterruption = lastSize sceneState.scaleBeforeInterruption = lastScale sceneState.alphaBeforeInterruption = lastAlpha - sceneState.offsetInterruptionDelta = Offset.Zero - sceneState.scaleInterruptionDelta = Scale.Zero - sceneState.alphaInterruptionDelta = 0f + sceneState.clearInterruptionDeltas() } } +private fun Element.SceneState.clearInterruptionDeltas() { + offsetInterruptionDelta = Offset.Zero + sizeInterruptionDelta = IntSize.Zero + scaleInterruptionDelta = Scale.Zero + alphaInterruptionDelta = 0f +} + +private fun Element.SceneState.clearValuesBeforeInterruption() { + offsetBeforeInterruption = Offset.Unspecified + scaleBeforeInterruption = Scale.Unspecified + alphaBeforeInterruption = Element.AlphaUnspecified +} + /** * Compute what [value] should be if we take the * [interruption progress][TransitionState.Transition.interruptionProgress] of [transition] into @@ -606,7 +636,6 @@ private fun interruptedAlpha( ) } -@OptIn(ExperimentalComposeUiApi::class) private fun ApproachMeasureScope.measure( layoutImpl: SceneTransitionLayoutImpl, scene: Scene, @@ -628,8 +657,6 @@ private fun ApproachMeasureScope.measure( // once. var maybePlaceable: Placeable? = null - fun Placeable.size() = IntSize(width, height) - val targetSize = computeValue( layoutImpl, @@ -644,15 +671,44 @@ private fun ApproachMeasureScope.measure( ::lerp, ) - return maybePlaceable - ?: measurable.measure( - Constraints.fixed( - targetSize.width.coerceAtLeast(0), - targetSize.height.coerceAtLeast(0), - ) + // The measurable was already measured, so we can't take interruptions into account here given + // that we are not allowed to measure the same measurable twice. + maybePlaceable?.let { placeable -> + sceneState.sizeBeforeInterruption = Element.SizeUnspecified + sceneState.sizeInterruptionDelta = IntSize.Zero + return placeable + } + + val interruptedSize = + computeInterruptedValue( + layoutImpl, + transition, + value = targetSize, + unspecifiedValue = Element.SizeUnspecified, + zeroValue = IntSize.Zero, + getValueBeforeInterruption = { sceneState.sizeBeforeInterruption }, + setValueBeforeInterruption = { sceneState.sizeBeforeInterruption = it }, + getInterruptionDelta = { sceneState.sizeInterruptionDelta }, + setInterruptionDelta = { sceneState.sizeInterruptionDelta = it }, + diff = { a, b -> IntSize(a.width - b.width, a.height - b.height) }, + add = { a, b, bProgress -> + IntSize( + (a.width + b.width * bProgress).roundToInt(), + (a.height + b.height * bProgress).roundToInt(), + ) + }, + ) + + return measurable.measure( + Constraints.fixed( + interruptedSize.width.coerceAtLeast(0), + interruptedSize.height.coerceAtLeast(0), ) + ) } +private fun Placeable.size(): IntSize = IntSize(width, height) + private fun ContentDrawScope.getDrawScale( layoutImpl: SceneTransitionLayoutImpl, scene: Scene, @@ -744,7 +800,11 @@ private fun ApproachMeasureScope.place( // No need to place the element in this scene if we don't want to draw it anyways. if (!shouldPlaceElement(layoutImpl, scene, element, transition)) { sceneState.lastOffset = Offset.Unspecified - sceneState.offsetBeforeInterruption = Offset.Unspecified + sceneState.lastScale = Scale.Unspecified + sceneState.lastAlpha = Element.AlphaUnspecified + + sceneState.clearValuesBeforeInterruption() + sceneState.clearInterruptionDeltas() return } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt new file mode 100644 index 000000000000..54c64fd721ec --- /dev/null +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/InterruptionHandler.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +/** + * A handler to specify how a transition should be interrupted. + * + * @see DefaultInterruptionHandler + * @see SceneTransitionsBuilder.interruptionHandler + */ +interface InterruptionHandler { + /** + * This function is called when [interrupted] is interrupted: it is currently animating between + * [interrupted.fromScene] and [interrupted.toScene], and we will now animate to + * [newTargetScene]. + * + * If this returns `null`, then the [default behavior][DefaultInterruptionHandler] will be used: + * we will animate from [interrupted.currentScene] and chaining will be enabled (see + * [InterruptionResult] for more information about chaining). + * + * @see InterruptionResult + */ + fun onInterruption( + interrupted: TransitionState.Transition, + newTargetScene: SceneKey, + ): InterruptionResult? +} + +/** + * The result of an interruption that specifies how we should handle a transition A => B now that we + * have to animate to C. + * + * For instance, if the interrupted transition was A => B and currentScene = B: + * - animateFrom = B && chain = true => there will be 2 transitions running in parallel, A => B and + * B => C. + * - animateFrom = A && chain = true => there will be 2 transitions running in parallel, B => A and + * A => C. + * - animateFrom = B && chain = false => there will be 1 transition running, B => C. + * - animateFrom = A && chain = false => there will be 1 transition running, A => C. + */ +class InterruptionResult( + /** + * The scene we should animate from when transitioning to C. + * + * Important: This **must** be either [TransitionState.Transition.fromScene] or + * [TransitionState.Transition.toScene] of the transition that was interrupted. + */ + val animateFrom: SceneKey, + + /** + * Whether chaining is enabled, i.e. if the new transition to C should run in parallel with the + * previous one(s) or if it should be the only remaining transition that is running. + */ + val chain: Boolean = true, +) + +/** + * The default interruption handler: we animate from [TransitionState.Transition.currentScene] and + * chaining is enabled. + */ +object DefaultInterruptionHandler : InterruptionHandler { + override fun onInterruption( + interrupted: TransitionState.Transition, + newTargetScene: SceneKey, + ): InterruptionResult { + return InterruptionResult( + animateFrom = interrupted.currentScene, + chain = true, + ) + } +} diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index 05dd5cc09dbf..0fc0053ce4a1 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -17,8 +17,6 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation -import androidx.compose.foundation.gestures.awaitEachGesture -import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation import androidx.compose.foundation.gestures.horizontalDrag @@ -26,12 +24,14 @@ import androidx.compose.foundation.gestures.verticalDrag import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventPass import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode +import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.input.pointer.util.addPointerInputChange @@ -45,8 +45,12 @@ import androidx.compose.ui.node.observeReads import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.Velocity +import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastForEach +import kotlin.coroutines.cancellation.CancellationException import kotlin.math.sign +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.isActive /** * Make an element draggable in the given [orientation]. @@ -163,75 +167,95 @@ internal class MultiPointerDraggableNode( return } - detectDragGestures( - orientation = orientation, - startDragImmediately = startDragImmediately, - onDragStart = { startedPosition, overSlop, pointersDown -> - velocityTracker.resetTracking() - onDragStarted(startedPosition, overSlop, pointersDown) - }, - onDrag = { controller, change, amount -> - velocityTracker.addPointerInputChange(change) - controller.onDrag(amount) - }, - onDragEnd = { controller -> - val viewConfiguration = currentValueOf(LocalViewConfiguration) - val maxVelocity = viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) } - val velocity = velocityTracker.calculateVelocity(maxVelocity) - controller.onStop( - velocity = - when (orientation) { - Orientation.Horizontal -> velocity.x - Orientation.Vertical -> velocity.y - }, - canChangeScene = true, - ) - }, - onDragCancel = { controller -> - controller.onStop(velocity = 0f, canChangeScene = true) - }, - ) + coroutineScope { + awaitPointerEventScope { + while (isActive) { + try { + detectDragGestures( + orientation = orientation, + startDragImmediately = startDragImmediately, + onDragStart = { startedPosition, overSlop, pointersDown -> + velocityTracker.resetTracking() + onDragStarted(startedPosition, overSlop, pointersDown) + }, + onDrag = { controller, change, amount -> + velocityTracker.addPointerInputChange(change) + controller.onDrag(amount) + }, + onDragEnd = { controller -> + val viewConfiguration = currentValueOf(LocalViewConfiguration) + val maxVelocity = + viewConfiguration.maximumFlingVelocity.let { Velocity(it, it) } + val velocity = velocityTracker.calculateVelocity(maxVelocity) + controller.onStop( + velocity = + when (orientation) { + Orientation.Horizontal -> velocity.x + Orientation.Vertical -> velocity.y + }, + canChangeScene = true, + ) + }, + onDragCancel = { controller -> + controller.onStop(velocity = 0f, canChangeScene = true) + }, + ) + } catch (exception: CancellationException) { + // If the coroutine scope is active, we can just restart the drag cycle. + if (!isActive) { + throw exception + } + } + } + } + } } -} -/** - * Detect drag gestures in the given [orientation]. - * - * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and - * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for: - * 1) starting the gesture immediately without requiring a drag >= touch slope; - * 2) passing the number of pointers down to [onDragStart]. - */ -private suspend fun PointerInputScope.detectDragGestures( - orientation: Orientation, - startDragImmediately: (startedPosition: Offset) -> Boolean, - onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, - onDragEnd: (controller: DragController) -> Unit, - onDragCancel: (controller: DragController) -> Unit, - onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit, -) { - awaitEachGesture { - val initialDown = awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial) + /** + * Detect drag gestures in the given [orientation]. + * + * This function is a mix of [androidx.compose.foundation.gestures.awaitDownAndSlop] and + * [androidx.compose.foundation.gestures.detectVerticalDragGestures] to add support for: + * 1) starting the gesture immediately without requiring a drag >= touch slope; + * 2) passing the number of pointers down to [onDragStart]. + */ + private suspend fun AwaitPointerEventScope.detectDragGestures( + orientation: Orientation, + startDragImmediately: (startedPosition: Offset) -> Boolean, + onDragStart: + (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit, + onDragEnd: (controller: DragController) -> Unit, + onDragCancel: (controller: DragController) -> Unit + ) { + // Wait for a consumable event in [PointerEventPass.Main] pass + val consumablePointer = awaitConsumableEvent().changes.first() + var overSlop = 0f val drag = - if (startDragImmediately(initialDown.position)) { - initialDown.consume() - initialDown + if (startDragImmediately(consumablePointer.position)) { + consumablePointer.consume() + consumablePointer } else { - val down = awaitFirstDown(requireUnconsumed = false) val onSlopReached = { change: PointerInputChange, over: Float -> change.consume() overSlop = over } - // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once - // it is public. + // TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it + // is public. val drag = when (orientation) { Orientation.Horizontal -> - awaitHorizontalTouchSlopOrCancellation(down.id, onSlopReached) + awaitHorizontalTouchSlopOrCancellation( + consumablePointer.id, + onSlopReached + ) Orientation.Vertical -> - awaitVerticalTouchSlopOrCancellation(down.id, onSlopReached) + awaitVerticalTouchSlopOrCancellation( + consumablePointer.id, + onSlopReached + ) } // Make sure that overSlop is not 0f. This can happen when the user drags by exactly @@ -240,16 +264,10 @@ private suspend fun PointerInputScope.detectDragGestures( // we intercept an ongoing swipe transition (i.e. startDragImmediately() returned // true). if (drag != null && overSlop == 0f) { - val deltaOffset = drag.position - initialDown.position - val delta = - when (orientation) { - Orientation.Horizontal -> deltaOffset.x - Orientation.Vertical -> deltaOffset.y - } + val delta = (drag.position - consumablePointer.position).toFloat() check(delta != 0f) { "delta is equal to 0" } overSlop = delta.sign } - drag } @@ -272,12 +290,12 @@ private suspend fun PointerInputScope.detectDragGestures( when (orientation) { Orientation.Horizontal -> horizontalDrag(drag.id) { - onDrag(controller, it, it.positionChange().x) + onDrag(controller, it, it.positionChange().toFloat()) it.consume() } Orientation.Vertical -> verticalDrag(drag.id) { - onDrag(controller, it, it.positionChange().y) + onDrag(controller, it, it.positionChange().toFloat()) it.consume() } } @@ -293,4 +311,35 @@ private suspend fun PointerInputScope.detectDragGestures( } } } + + private suspend fun AwaitPointerEventScope.awaitConsumableEvent(): PointerEvent { + fun canBeConsumed(changes: List<PointerInputChange>): Boolean { + // All pointers must be: + return changes.fastAll { + // A) recently pressed: even if the event has already been consumed, we can still + // use the recently added finger event to determine whether to initiate dragging the + // scene. + it.changedToDownIgnoreConsumed() || + // B) unconsumed AND in a new position (on the current axis) + it.positionChange().toFloat() != 0f + } + } + + var event: PointerEvent + do { + // To allow the descendants with the opportunity to consume the event, we wait for it in + // the Main pass. + event = awaitPointerEvent() + } while (!canBeConsumed(event.changes)) + + // We found a consumable event in the Main pass + return event + } + + private fun Offset.toFloat(): Float { + return when (orientation) { + Orientation.Vertical -> y + Orientation.Horizontal -> x + } + } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt index 5a2f85ad163c..1fa6b3f7d6c0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt @@ -75,6 +75,7 @@ internal fun Modifier.nestedScrollToScene( orientation: Orientation, topOrLeftBehavior: NestedScrollBehavior, bottomOrRightBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: () -> Boolean, ) = this then NestedScrollToSceneElement( @@ -82,6 +83,7 @@ internal fun Modifier.nestedScrollToScene( orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, ) private data class NestedScrollToSceneElement( @@ -89,6 +91,7 @@ private data class NestedScrollToSceneElement( private val orientation: Orientation, private val topOrLeftBehavior: NestedScrollBehavior, private val bottomOrRightBehavior: NestedScrollBehavior, + private val isExternalOverscrollGesture: () -> Boolean, ) : ModifierNodeElement<NestedScrollToSceneNode>() { override fun create() = NestedScrollToSceneNode( @@ -96,6 +99,7 @@ private data class NestedScrollToSceneElement( orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, ) override fun update(node: NestedScrollToSceneNode) { @@ -104,6 +108,7 @@ private data class NestedScrollToSceneElement( orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, ) } @@ -121,6 +126,7 @@ private class NestedScrollToSceneNode( orientation: Orientation, topOrLeftBehavior: NestedScrollBehavior, bottomOrRightBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: () -> Boolean, ) : DelegatingNode() { private var priorityNestedScrollConnection: PriorityNestedScrollConnection = scenePriorityNestedScrollConnection( @@ -128,6 +134,7 @@ private class NestedScrollToSceneNode( orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, ) private var nestedScrollNode: DelegatableNode = @@ -150,6 +157,7 @@ private class NestedScrollToSceneNode( orientation: Orientation, topOrLeftBehavior: NestedScrollBehavior, bottomOrRightBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: () -> Boolean, ) { // Clean up the old nested scroll connection priorityNestedScrollConnection.reset() @@ -162,6 +170,7 @@ private class NestedScrollToSceneNode( orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, ) nestedScrollNode = nestedScrollModifierNode( @@ -177,11 +186,13 @@ private fun scenePriorityNestedScrollConnection( orientation: Orientation, topOrLeftBehavior: NestedScrollBehavior, bottomOrRightBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: () -> Boolean, ) = NestedScrollHandlerImpl( layoutImpl = layoutImpl, orientation = orientation, topOrLeftBehavior = topOrLeftBehavior, bottomOrRightBehavior = bottomOrRightBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, ) .connection 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 983cff83ed57..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 @@ -19,6 +19,7 @@ package com.android.compose.animation.scene import androidx.compose.runtime.snapshotFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf /** * A scene transition state. @@ -33,14 +34,26 @@ import kotlinx.coroutines.flow.distinctUntilChanged * [ObservableTransitionState.Transition.toScene] will never be equal, while * [TransitionState.Transition.fromScene] and [TransitionState.Transition.toScene] can be equal. */ -sealed class ObservableTransitionState { +sealed interface ObservableTransitionState { + /** + * The current effective scene. If a new transition was triggered, it would start from this + * scene. + */ + fun currentScene(): Flow<SceneKey> { + return when (this) { + is Idle -> flowOf(currentScene) + is Transition -> currentScene + } + } + /** No transition/animation is currently running. */ - data class Idle(val scene: SceneKey) : ObservableTransitionState() + data class Idle(val currentScene: SceneKey) : ObservableTransitionState /** There is a transition animating between two scenes. */ - data class Transition( + class Transition( val fromScene: SceneKey, val toScene: SceneKey, + val currentScene: Flow<SceneKey>, val progress: Flow<Float>, /** @@ -60,7 +73,17 @@ sealed class ObservableTransitionState { * the transition completes/settles. */ val isUserInputOngoing: Flow<Boolean>, - ) : ObservableTransitionState() + ) : 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) + } } /** @@ -76,6 +99,7 @@ fun SceneTransitionLayoutState.observableTransitionState(): Flow<ObservableTrans ObservableTransitionState.Transition( fromScene = state.fromScene, toScene = state.toScene, + currentScene = snapshotFlow { state.currentScene }, progress = snapshotFlow { state.progress }, isInitiatedByUserInput = state.isInitiatedByUserInput, isUserInputOngoing = snapshotFlow { state.isUserInputOngoing }, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 339868c9fbc9..6fef33c797d9 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -141,23 +141,27 @@ internal class SceneScopeImpl( override fun Modifier.horizontalNestedScrollToScene( leftBehavior: NestedScrollBehavior, rightBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: () -> Boolean, ): Modifier = nestedScrollToScene( layoutImpl = layoutImpl, orientation = Orientation.Horizontal, topOrLeftBehavior = leftBehavior, bottomOrRightBehavior = rightBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, ) override fun Modifier.verticalNestedScrollToScene( topBehavior: NestedScrollBehavior, - bottomBehavior: NestedScrollBehavior + bottomBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: () -> Boolean, ): Modifier = nestedScrollToScene( layoutImpl = layoutImpl, orientation = Orientation.Vertical, topOrLeftBehavior = topBehavior, bottomOrRightBehavior = bottomBehavior, + isExternalOverscrollGesture = isExternalOverscrollGesture, ) override fun Modifier.noResizeDuringTransitions(): Modifier { diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index c7c874c1185d..11e711ace971 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -250,6 +250,7 @@ interface BaseSceneScope : ElementStateScope { fun Modifier.horizontalNestedScrollToScene( leftBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, rightBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + isExternalOverscrollGesture: () -> Boolean = { false }, ): Modifier /** @@ -262,6 +263,7 @@ interface BaseSceneScope : ElementStateScope { fun Modifier.verticalNestedScrollToScene( topBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + isExternalOverscrollGesture: () -> Boolean = { false }, ): Modifier /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt index d383cec324d3..7856498aa365 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt @@ -126,6 +126,10 @@ internal class SceneTransitionLayoutImpl( orientation = Orientation.Vertical, coroutineScope = coroutineScope, ) + + // Make sure that the state is created on the same thread (most probably the main thread) + // than this STLImpl. + state.checkThread() } internal fun draggableHandler(orientation: Orientation): DraggableHandlerImpl = diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 5fda77a3e0ae..a5b6d2486168 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -26,8 +26,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.SnapshotStateList +import androidx.compose.runtime.setValue import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastFilter import androidx.compose.ui.util.fastForEach @@ -117,6 +119,9 @@ sealed interface MutableSceneTransitionLayoutState : SceneTransitionLayoutState coroutineScope: CoroutineScope, transitionKey: TransitionKey? = null, ): TransitionState.Transition? + + /** Immediately snap to the given [scene]. */ + fun snapToScene(scene: SceneKey) } /** @@ -227,6 +232,9 @@ sealed interface TransitionState { */ abstract val progress: Float + /** The current velocity of [progress], in progress units. */ + abstract val progressVelocity: Float + /** Whether the transition was triggered by user input rather than being programmatic. */ abstract val isInitiatedByUserInput: Boolean @@ -368,14 +376,17 @@ internal abstract class BaseSceneTransitionLayoutState( // TODO(b/290930950): Remove this flag. internal var enableInterruptions: Boolean, ) : SceneTransitionLayoutState { + private val creationThread: Thread = Thread.currentThread() + /** * The current [TransitionState]. This list will either be: * 1. A list with a single [TransitionState.Idle] element, when we are idle. * 2. A list with one or more [TransitionState.Transition], when we are transitioning. */ @VisibleForTesting - internal val transitionStates: MutableList<TransitionState> = - SnapshotStateList<TransitionState>().apply { add(TransitionState.Idle(initialScene)) } + internal var transitionStates: List<TransitionState> by + mutableStateOf(listOf(TransitionState.Idle(initialScene))) + private set override val transitionState: TransitionState get() = transitionStates.last() @@ -411,6 +422,20 @@ internal abstract class BaseSceneTransitionLayoutState( */ internal abstract fun CoroutineScope.onChangeScene(scene: SceneKey) + internal fun checkThread() { + val current = Thread.currentThread() + if (current !== creationThread) { + error( + """ + Only the original thread that created a SceneTransitionLayoutState can mutate it + Expected: ${creationThread.name} + Current: ${current.name} + """ + .trimIndent() + ) + } + } + override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean { val transition = currentTransition ?: return false return transition.isTransitioning(from, to) @@ -422,14 +447,21 @@ internal abstract class BaseSceneTransitionLayoutState( } /** - * Start a new [transition], instantly interrupting any ongoing transition if there was one. + * Start a new [transition]. + * + * If [chain] is `true`, then the transitions will simply be added to [currentTransitions] and + * will run in parallel to the current transitions. If [chain] is `false`, then the list of + * [currentTransitions] will be cleared and [transition] will be the only running transition. * * Important: you *must* call [finishTransition] once the transition is finished. */ internal fun startTransition( transition: TransitionState.Transition, transitionKey: TransitionKey?, + chain: Boolean = true, ) { + checkThread() + // Compute the [TransformationSpec] when the transition starts. val fromScene = transition.fromScene val toScene = transition.toScene @@ -454,7 +486,7 @@ internal abstract class BaseSceneTransitionLayoutState( if (!enableInterruptions) { // Set the current transition. check(transitionStates.size == 1) - transitionStates[0] = transition + transitionStates = listOf(transition) return } @@ -462,35 +494,17 @@ internal abstract class BaseSceneTransitionLayoutState( is TransitionState.Idle -> { // Replace [Idle] by [transition]. check(transitionStates.size == 1) - transitionStates[0] = transition + transitionStates = listOf(transition) } is TransitionState.Transition -> { - // Force the current transition to finish to currentScene. - currentState.finish().invokeOnCompletion { - // Make sure [finishTransition] is called at the end of the transition. - finishTransition(currentState, currentState.currentScene) - } + // Force the current transition to finish to currentScene. The transition will call + // [finishTransition] once it's finished. + currentState.finish() - // Check that we don't have too many concurrent transitions. - if (transitionStates.size >= MAX_CONCURRENT_TRANSITIONS) { - Log.wtf( - TAG, - buildString { - appendLine("Potential leak detected in SceneTransitionLayoutState!") - appendLine( - " Some transition(s) never called STLState.finishTransition()." - ) - appendLine(" Transitions (size=${transitionStates.size}):") - transitionStates.fastForEach { state -> - val transition = state as TransitionState.Transition - val from = transition.fromScene - val to = transition.toScene - val indicator = - if (finishedTransitions.contains(transition)) "x" else " " - appendLine(" [$indicator] $from => $to ($transition)") - } - } - ) + val tooManyTransitions = transitionStates.size >= MAX_CONCURRENT_TRANSITIONS + val clearCurrentTransitions = !chain || tooManyTransitions + if (clearCurrentTransitions) { + if (tooManyTransitions) logTooManyTransitions() // Force finish all transitions. while (currentTransitions.isNotEmpty()) { @@ -502,15 +516,33 @@ internal abstract class BaseSceneTransitionLayoutState( // we end up only with the new transition after appending it. check(transitionStates.size == 1) check(transitionStates[0] is TransitionState.Idle) - transitionStates.clear() + transitionStates = listOf(transition) + } else { + // Append the new transition. + transitionStates = transitionStates + transition } - - // Append the new transition. - transitionStates.add(transition) } } } + private fun logTooManyTransitions() { + Log.wtf( + TAG, + buildString { + appendLine("Potential leak detected in SceneTransitionLayoutState!") + appendLine(" Some transition(s) never called STLState.finishTransition().") + appendLine(" Transitions (size=${transitionStates.size}):") + transitionStates.fastForEach { state -> + val transition = state as TransitionState.Transition + val from = transition.fromScene + val to = transition.toScene + val indicator = if (finishedTransitions.contains(transition)) "x" else " " + appendLine(" [$indicator] $from => $to ($transition)") + } + } + ) + } + private fun cancelActiveTransitionLinks() { for ((link, linkedTransition) in activeTransitionLinks) { link.target.finishTransition(linkedTransition, linkedTransition.currentScene) @@ -548,6 +580,8 @@ internal abstract class BaseSceneTransitionLayoutState( * nothing if [transition] was interrupted since it was started. */ internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) { + checkThread() + val existingIdleScene = finishedTransitions[transition] if (existingIdleScene != null) { // This transition was already finished. @@ -558,6 +592,7 @@ internal abstract class BaseSceneTransitionLayoutState( return } + val transitionStates = this.transitionStates if (!transitionStates.contains(transition)) { // This transition was already removed from transitionStates. return @@ -576,25 +611,42 @@ internal abstract class BaseSceneTransitionLayoutState( var lastRemovedIdleScene: SceneKey? = null // Remove all first n finished transitions. - while (transitionStates.isNotEmpty()) { - val firstTransition = transitionStates[0] - if (!finishedTransitions.contains(firstTransition)) { + var i = 0 + val nStates = transitionStates.size + while (i < nStates) { + val t = transitionStates[i] + if (!finishedTransitions.contains(t)) { // Stop here. break } - // Remove the transition from the list and from the set of finished transitions. - transitionStates.removeAt(0) - lastRemovedIdleScene = finishedTransitions.remove(firstTransition) + // Remove the transition from the set of finished transitions. + lastRemovedIdleScene = finishedTransitions.remove(t) + i++ } // If all transitions are finished, we are idle. - if (transitionStates.isEmpty()) { + if (i == nStates) { check(finishedTransitions.isEmpty()) - transitionStates.add(TransitionState.Idle(checkNotNull(lastRemovedIdleScene))) + this.transitionStates = listOf(TransitionState.Idle(checkNotNull(lastRemovedIdleScene))) + } else if (i > 0) { + this.transitionStates = transitionStates.subList(fromIndex = i, toIndex = nStates) } } + fun snapToScene(scene: SceneKey) { + checkThread() + + // Force finish all transitions. + while (currentTransitions.isNotEmpty()) { + val transition = transitionStates[0] as TransitionState.Transition + finishTransition(transition, transition.currentScene) + } + + check(transitionStates.size == 1) + transitionStates = listOf(TransitionState.Idle(scene)) + } + private fun finishActiveTransitionLinks(idleScene: SceneKey) { val previousTransition = this.transitionState as? TransitionState.Transition ?: return for ((link, linkedTransition) in activeTransitionLinks) { @@ -723,6 +775,8 @@ internal class MutableSceneTransitionLayoutStateImpl( coroutineScope: CoroutineScope, transitionKey: TransitionKey?, ): TransitionState.Transition? { + checkThread() + return coroutineScope.animateToScene( layoutState = this@MutableSceneTransitionLayoutStateImpl, target = targetScene, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt index b46614397ff4..0f6a1d276578 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt @@ -44,6 +44,7 @@ internal constructor( internal val defaultSwipeSpec: SpringSpec<Float>, internal val transitionSpecs: List<TransitionSpecImpl>, internal val overscrollSpecs: List<OverscrollSpecImpl>, + internal val interruptionHandler: InterruptionHandler, ) { private val transitionCache = mutableMapOf< @@ -145,6 +146,7 @@ internal constructor( defaultSwipeSpec = DefaultSwipeSpec, transitionSpecs = emptyList(), overscrollSpecs = emptyList(), + interruptionHandler = DefaultInterruptionHandler, ) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index 6bc397e86cfa..a4682ff2a885 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -40,6 +40,12 @@ interface SceneTransitionsBuilder { var defaultSwipeSpec: SpringSpec<Float> /** + * The [InterruptionHandler] used when transitions are interrupted. Defaults to + * [DefaultInterruptionHandler]. + */ + var interruptionHandler: InterruptionHandler + + /** * Define the default animation to be played when transitioning [to] the specified scene, from * any scene. For the animation specification to apply only when transitioning between two * specific scenes, use [from] instead. diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt index 1c9080fa085d..802ab1f2eebb 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt @@ -47,12 +47,14 @@ internal fun transitionsImpl( return SceneTransitions( impl.defaultSwipeSpec, impl.transitionSpecs, - impl.transitionOverscrollSpecs + impl.transitionOverscrollSpecs, + impl.interruptionHandler, ) } private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder { override var defaultSwipeSpec: SpringSpec<Float> = SceneTransitions.DefaultSwipeSpec + override var interruptionHandler: InterruptionHandler = DefaultInterruptionHandler val transitionSpecs = mutableListOf<TransitionSpecImpl>() val transitionOverscrollSpecs = mutableListOf<OverscrollSpecImpl>() diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt index 73393a1ab0cf..79f126d24561 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transition/link/LinkedTransition.kt @@ -45,5 +45,8 @@ internal class LinkedTransition( override val progress: Float get() = originalTransition.progress + override val progressVelocity: Float + get() = originalTransition.progressVelocity + override fun finish(): Job = originalTransition.finish() } diff --git a/packages/SystemUI/compose/scene/tests/Android.bp b/packages/SystemUI/compose/scene/tests/Android.bp index af1389680bd2..3509504b6357 100644 --- a/packages/SystemUI/compose/scene/tests/Android.bp +++ b/packages/SystemUI/compose/scene/tests/Android.bp @@ -25,8 +25,8 @@ package { android_test { name: "PlatformComposeSceneTransitionLayoutTests", manifest: "AndroidManifest.xml", + defaults: ["MotionTestDefaults"], test_suites: ["device-tests"], - certificate: "platform", srcs: [ "src/**/*.kt", @@ -38,7 +38,7 @@ android_test { static_libs: [ "PlatformComposeSceneTransitionLayoutTestsUtils", - + "PlatformMotionTestingCompose", "androidx.test.runner", "androidx.test.ext.junit", @@ -48,7 +48,7 @@ android_test { "truth", ], - + asset_dirs: ["goldens"], kotlincflags: ["-Xjvm-default=all"], use_resource_processor: true, } diff --git a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml index 1a9172ee20e0..174ad30a8f1d 100644 --- a/packages/SystemUI/compose/scene/tests/AndroidManifest.xml +++ b/packages/SystemUI/compose/scene/tests/AndroidManifest.xml @@ -17,6 +17,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.compose.animation.scene.tests" > + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application> <uses-library android:name="android.test.runner" /> </application> diff --git a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredHeightOnly.json b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredHeightOnly.json new file mode 100644 index 000000000000..0843663baed4 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredHeightOnly.json @@ -0,0 +1,41 @@ +{ + "frame_ids": [ + "before", + 0, + 16, + 32, + 48, + "after" + ], + "features": [ + { + "name": "Bar_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "width": 200, + "height": 100 + }, + { + "width": 200, + "height": 90 + }, + { + "width": 200, + "height": 80 + }, + { + "width": 200, + "height": 70 + }, + { + "width": 200, + "height": 60 + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeEnter.json b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeEnter.json new file mode 100644 index 000000000000..2df440912bfc --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeEnter.json @@ -0,0 +1,41 @@ +{ + "frame_ids": [ + "before", + 0, + 16, + 32, + 48, + "after" + ], + "features": [ + { + "name": "Bar_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "width": 100, + "height": 100 + }, + { + "width": 125.14286, + "height": 90 + }, + { + "width": 150, + "height": 80 + }, + { + "width": 175.14285, + "height": 70 + }, + { + "width": 200, + "height": 60 + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeExit.json b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeExit.json new file mode 100644 index 000000000000..2b0a9541a394 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredSizeExit.json @@ -0,0 +1,41 @@ +{ + "frame_ids": [ + "before", + 0, + 16, + 32, + 48, + "after" + ], + "features": [ + { + "name": "Bar_size", + "type": "dpSize", + "data_points": [ + { + "width": 100, + "height": 100 + }, + { + "width": 100, + "height": 100 + }, + { + "width": 125.14286, + "height": 90 + }, + { + "width": 150, + "height": 80 + }, + { + "width": 175.14285, + "height": 70 + }, + { + "type": "not_found" + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/goldens/testAnchoredWidthOnly.json b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredWidthOnly.json new file mode 100644 index 000000000000..027df299d15e --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/goldens/testAnchoredWidthOnly.json @@ -0,0 +1,41 @@ +{ + "frame_ids": [ + "before", + 0, + 16, + 32, + 48, + "after" + ], + "features": [ + { + "name": "Bar_size", + "type": "dpSize", + "data_points": [ + { + "type": "not_found" + }, + { + "width": 100, + "height": 60 + }, + { + "width": 125.14286, + "height": 60 + }, + { + "width": 150, + "height": 60 + }, + { + "width": 175.14285, + "height": 60 + }, + { + "width": 200, + "height": 60 + } + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index 1fd1bf4a56e2..8625482d5f71 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -32,12 +32,11 @@ import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithPreview import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC -import com.android.compose.animation.scene.TransitionState.Idle import com.android.compose.animation.scene.TransitionState.Transition +import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.MonotonicClockTestScope import com.android.compose.test.runMonotonicClockTest import com.google.common.truth.Truth.assertThat -import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch @@ -103,12 +102,16 @@ class DraggableHandlerTest { val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical) val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) - fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) = + fun nestedScrollConnection( + nestedScrollBehavior: NestedScrollBehavior, + isExternalOverscrollGesture: Boolean = false + ) = NestedScrollHandlerImpl( layoutImpl = layoutImpl, orientation = draggableHandler.orientation, topOrLeftBehavior = nestedScrollBehavior, bottomOrRightBehavior = nestedScrollBehavior, + isExternalOverscrollGesture = { isExternalOverscrollGesture } ) .connection @@ -145,10 +148,8 @@ class DraggableHandlerTest { } fun assertIdle(currentScene: SceneKey) { - assertThat(transitionState).isInstanceOf(Idle::class.java) - assertWithMessage("currentScene does not match") - .that(transitionState.currentScene) - .isEqualTo(currentScene) + assertThat(transitionState).isIdle() + assertThat(transitionState).hasCurrentScene(currentScene) } fun assertTransition( @@ -158,34 +159,12 @@ class DraggableHandlerTest { progress: Float? = null, isUserInputOngoing: Boolean? = null ) { - assertThat(transitionState).isInstanceOf(Transition::class.java) - val transition = transitionState as Transition - - if (currentScene != null) - assertWithMessage("currentScene does not match") - .that(transition.currentScene) - .isEqualTo(currentScene) - - if (fromScene != null) - assertWithMessage("fromScene does not match") - .that(transition.fromScene) - .isEqualTo(fromScene) - - if (toScene != null) - assertWithMessage("toScene does not match") - .that(transition.toScene) - .isEqualTo(toScene) - - if (progress != null) - assertWithMessage("progress does not match") - .that(transition.progress) - .isWithin(0f) // returns true when comparing 0.0f with -0.0f - .of(progress) - - if (isUserInputOngoing != null) - assertWithMessage("isUserInputOngoing does not match") - .that(transition.isUserInputOngoing) - .isEqualTo(isUserInputOngoing) + val transition = assertThat(transitionState).isTransition() + currentScene?.let { assertThat(transition).hasCurrentScene(it) } + fromScene?.let { assertThat(transition).hasFromScene(it) } + toScene?.let { assertThat(transition).hasToScene(it) } + progress?.let { assertThat(transition).hasProgress(it) } + isUserInputOngoing?.let { assertThat(transition).hasIsUserInputOngoing(it) } } fun onDragStarted( @@ -801,6 +780,26 @@ class DraggableHandlerTest { } @Test + fun flingAfterScrollStartedByExternalOverscrollGesture() = runGestureTest { + val nestedScroll = + nestedScrollConnection( + nestedScrollBehavior = EdgeWithPreview, + isExternalOverscrollGesture = true + ) + + // scroll not consumed in child + nestedScroll.scroll( + available = downOffset(fractionOfScreen = 0.1f), + ) + + // scroll offsetY10 is all available for parents + nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f)) + assertTransition(SceneA) + + nestedScroll.preFling(available = Velocity(0f, velocityThreshold)) + } + + @Test fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) nestedScroll.preFling(available = Velocity(0f, velocityThreshold)) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 92e1b2cd030c..6e114e348017 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -20,7 +20,6 @@ import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween -import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.scrollable @@ -43,7 +42,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.layout.approachLayout @@ -58,17 +56,21 @@ import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.subjects.assertThat +import com.android.compose.test.assertSizeIsEqualTo import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -78,7 +80,6 @@ class ElementTest { @get:Rule val rule = createComposeRule() @Composable - @OptIn(ExperimentalComposeUiApi::class) private fun SceneScope.Element( key: ElementKey, size: Dp, @@ -239,9 +240,9 @@ class ElementTest { changeScene(SceneC) } - at(2 * frameDuration) { onElement(TestElements.Bar).assertIsNotDisplayed() } + at(3 * frameDuration) { onElement(TestElements.Bar).assertIsNotDisplayed() } - at(3 * frameDuration) { onElement(TestElements.Bar).assertDoesNotExist() } + at(4 * frameDuration) { onElement(TestElements.Bar).assertDoesNotExist() } } } @@ -496,7 +497,6 @@ class ElementTest { } @Test - @OptIn(ExperimentalFoundationApi::class) fun elementModifierNodeIsRecycledInLazyLayouts() = runTest { val nPages = 2 val pagerState = PagerState(currentPage = 0) { nPages } @@ -581,6 +581,7 @@ class ElementTest { } @Test + @Ignore("b/341072461") fun existingElementsDontRecomposeWhenTransitionStateChanges() { var fooCompositions = 0 @@ -606,6 +607,43 @@ class ElementTest { } } + @Test + // TODO(b/341072461): Remove this test. + fun layoutGetsCurrentTransitionStateFromComposition() { + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutStateImpl( + SceneA, + transitions { + from(SceneA, to = SceneB) { + scaleSize(TestElements.Foo, width = 2f, height = 2f) + } + } + ) + } + + rule.setContent { + SceneTransitionLayout(state) { + scene(SceneA) { Box(Modifier.element(TestElements.Foo).size(20.dp)) } + scene(SceneB) {} + } + } + + // Pause the clock to block recompositions. + rule.mainClock.autoAdvance = false + + // Change the current transition. + rule.runOnUiThread { + state.startTransition( + transition(from = SceneA, to = SceneB, progress = { 0.5f }), + transitionKey = null, + ) + } + + // The size of Foo should still be 20dp given that the new state was not composed yet. + rule.onNode(isElement(TestElements.Foo)).assertSizeIsEqualTo(20.dp, 20.dp) + } + private fun setupOverscrollScenario( layoutWidth: Dp, layoutHeight: Dp, @@ -619,11 +657,13 @@ class ElementTest { var touchSlop = 0f val state = - MutableSceneTransitionLayoutState( - initialScene = SceneA, - transitions = transitions(sceneTransitions), - ) - as MutableSceneTransitionLayoutStateImpl + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + initialScene = SceneA, + transitions = transitions(sceneTransitions), + ) + as MutableSceneTransitionLayoutStateImpl + } rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop @@ -654,8 +694,7 @@ class ElementTest { } } - assertThat(state.currentTransition).isNull() - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(state.transitionState).isIdle() // Swipe by half of verticalSwipeDistance. rule.onRoot().performTouchInput { @@ -691,9 +730,9 @@ class ElementTest { val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) fooElement.assertTopPositionInRootIsEqualTo(0.dp) - val transition = state.currentTransition + val transition = assertThat(state.transitionState).isTransition() assertThat(transition).isNotNull() - assertThat(transition!!.progress).isEqualTo(0.5f) + assertThat(transition).hasProgress(0.5f) assertThat(animatedFloat).isEqualTo(50f) rule.onRoot().performTouchInput { @@ -702,8 +741,8 @@ class ElementTest { } // Scroll 150% (Scene B overscroll by 50%) - assertThat(transition.progress).isEqualTo(1.5f) - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(transition).hasProgress(1.5f) + assertThat(transition).hasOverscrollSpec() fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f) // animatedFloat cannot overflow (canOverflow = false) assertThat(animatedFloat).isEqualTo(100f) @@ -714,8 +753,8 @@ class ElementTest { } // Scroll 250% (Scene B overscroll by 150%) - assertThat(transition.progress).isEqualTo(2.5f) - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(transition).hasProgress(2.5f) + assertThat(transition).hasOverscrollSpec() fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f) assertThat(animatedFloat).isEqualTo(100f) } @@ -730,16 +769,18 @@ class ElementTest { val layoutHeight = 400.dp val state = - MutableSceneTransitionLayoutState( - initialScene = SceneB, - transitions = - transitions { - overscroll(SceneB, Orientation.Vertical) { - translate(TestElements.Foo, y = overscrollTranslateY) + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + initialScene = SceneB, + transitions = + transitions { + overscroll(SceneB, Orientation.Vertical) { + translate(TestElements.Foo, y = overscrollTranslateY) + } } - } - ) - as MutableSceneTransitionLayoutStateImpl + ) + as MutableSceneTransitionLayoutStateImpl + } rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop @@ -766,8 +807,7 @@ class ElementTest { } } - assertThat(state.currentTransition).isNull() - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(state.transitionState).isIdle() val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true) fooElement.assertTopPositionInRootIsEqualTo(0.dp) @@ -779,10 +819,9 @@ class ElementTest { moveBy(Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f), delayMillis = 1_000) } - val transition = state.currentTransition - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() - assertThat(transition).isNotNull() - assertThat(transition!!.progress).isEqualTo(-0.5f) + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasOverscrollSpec() + assertThat(transition).hasProgress(-0.5f) fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f) rule.onRoot().performTouchInput { @@ -791,8 +830,8 @@ class ElementTest { } // Scroll 150% (Scene B overscroll by 50%) - assertThat(transition.progress).isEqualTo(-1.5f) - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(transition).hasProgress(-1.5f) + assertThat(transition).hasOverscrollSpec() fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f) } @@ -825,13 +864,12 @@ class ElementTest { moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000) } - val transition = state.currentTransition - assertThat(transition).isNotNull() + val transition = assertThat(state.transitionState).isTransition() assertThat(animatedFloat).isEqualTo(100f) // Scroll 150% (100% scroll + 50% overscroll) - assertThat(transition!!.progress).isEqualTo(1.5f) - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(transition).hasProgress(1.5f) + assertThat(transition).hasOverscrollSpec() fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 0.5f) assertThat(animatedFloat).isEqualTo(100f) @@ -841,8 +879,8 @@ class ElementTest { } // Scroll 250% (100% scroll + 150% overscroll) - assertThat(transition.progress).isEqualTo(2.5f) - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(transition).hasProgress(2.5f) + assertThat(transition).hasOverscrollSpec() fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * 1.5f) assertThat(animatedFloat).isEqualTo(100f) } @@ -882,13 +920,11 @@ class ElementTest { moveBy(Offset(0f, layoutHeight.toPx() * 0.5f), delayMillis = 1_000) } - val transition = state.currentTransition - assertThat(transition).isNotNull() - transition as TransitionState.HasOverscrollProperties + val transition = assertThat(state.transitionState).isTransition() // Scroll 150% (100% scroll + 50% overscroll) - assertThat(transition.progress).isEqualTo(1.5f) - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() + assertThat(transition).hasProgress(1.5f) + assertThat(transition).hasOverscrollSpec() fooElement.assertTopPositionInRootIsEqualTo(layoutHeight * (transition.progress - 1f)) assertThat(animatedFloat).isEqualTo(100f) @@ -900,8 +936,8 @@ class ElementTest { rule.waitUntil(timeoutMillis = 10_000) { transition.progress < 1f } assertThat(transition.progress).isLessThan(1f) - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() - assertThat(transition.bouncingScene).isEqualTo(transition.toScene) + assertThat(transition).hasOverscrollSpec() + assertThat(transition).hasBouncingScene(transition.toScene) assertThat(animatedFloat).isEqualTo(100f) } @@ -911,32 +947,36 @@ class ElementTest { val duration = 4 * 16 val state = - MutableSceneTransitionLayoutState( - SceneA, - transitions { - // Foo is at the top left corner of scene A. We make it disappear during A => B - // to the right edge so it translates to the right. - from(SceneA, to = SceneB) { - spec = tween(duration, easing = LinearEasing) - translate( - TestElements.Foo, - edge = Edge.Right, - startsOutsideLayoutBounds = false, - ) - } + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + SceneA, + transitions { + // Foo is at the top left corner of scene A. We make it disappear during A + // => B + // to the right edge so it translates to the right. + from(SceneA, to = SceneB) { + spec = tween(duration, easing = LinearEasing) + translate( + TestElements.Foo, + edge = Edge.Right, + startsOutsideLayoutBounds = false, + ) + } - // Bar is at the top right corner of scene C. We make it appear during B => C - // from the left edge so it translates to the right at same time as Foo. - from(SceneB, to = SceneC) { - spec = tween(duration, easing = LinearEasing) - translate( - TestElements.Bar, - edge = Edge.Left, - startsOutsideLayoutBounds = false, - ) + // Bar is at the top right corner of scene C. We make it appear during B => + // C + // from the left edge so it translates to the right at same time as Foo. + from(SceneB, to = SceneC) { + spec = tween(duration, easing = LinearEasing) + translate( + TestElements.Bar, + edge = Edge.Left, + startsOutsideLayoutBounds = false, + ) + } } - } - ) + ) + } val layoutSize = 150.dp val elemSize = 50.dp @@ -980,13 +1020,13 @@ class ElementTest { val transitions = state.currentTransitions assertThat(transitions).hasSize(2) - assertThat(transitions[0].fromScene).isEqualTo(SceneA) - assertThat(transitions[0].toScene).isEqualTo(SceneB) - assertThat(transitions[0].progress).isEqualTo(0f) + assertThat(transitions[0]).hasFromScene(SceneA) + assertThat(transitions[0]).hasToScene(SceneB) + assertThat(transitions[0]).hasProgress(0f) - assertThat(transitions[1].fromScene).isEqualTo(SceneB) - assertThat(transitions[1].toScene).isEqualTo(SceneC) - assertThat(transitions[1].progress).isEqualTo(0f) + assertThat(transitions[1]).hasFromScene(SceneB) + assertThat(transitions[1]).hasToScene(SceneC) + assertThat(transitions[1]).hasProgress(0f) // First frame: both are at x = 0dp. For the whole transition, Foo is at y = 0dp and Bar is // at y = layoutSize - elementSoze = 100dp. @@ -1032,53 +1072,66 @@ class ElementTest { val duration = 4 * 16 val state = - MutableSceneTransitionLayoutStateImpl( - SceneA, - transitions { - from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) } - from(SceneB, to = SceneC) { spec = tween(duration, easing = LinearEasing) } - }, - enableInterruptions = false, - ) + rule.runOnUiThread { + MutableSceneTransitionLayoutStateImpl( + SceneA, + transitions { + from(SceneA, to = SceneB) { spec = tween(duration, easing = LinearEasing) } + from(SceneB, to = SceneC) { spec = tween(duration, easing = LinearEasing) } + }, + ) + } val layoutSize = DpSize(200.dp, 100.dp) - val fooSize = DpSize(20.dp, 10.dp) @Composable - fun SceneScope.Foo(modifier: Modifier = Modifier) { - Box(modifier.element(TestElements.Foo).size(fooSize)) + fun SceneScope.Foo(size: Dp, modifier: Modifier = Modifier) { + Box(modifier.element(TestElements.Foo).size(size)) } + // The size of Foo when idle in A, B or C. + val sizeInA = 10.dp + val sizeInB = 30.dp + val sizeInC = 50.dp + + lateinit var layoutImpl: SceneTransitionLayoutImpl rule.setContent { - SceneTransitionLayout(state, Modifier.size(layoutSize)) { + SceneTransitionLayoutForTesting( + state, + Modifier.size(layoutSize), + onLayoutImpl = { layoutImpl = it }, + ) { // In scene A, Foo is aligned at the TopStart. scene(SceneA) { - Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopStart)) } + Box(Modifier.fillMaxSize()) { Foo(sizeInA, Modifier.align(Alignment.TopStart)) } + } + + // In scene C, Foo is aligned at the BottomEnd, so it moves vertically when coming + // from B. We put it before (below) scene B so that we can check that interruptions + // values and deltas are properly cleared once all transitions are done. + scene(SceneC) { + Box(Modifier.fillMaxSize()) { + Foo(sizeInC, Modifier.align(Alignment.BottomEnd)) + } } // In scene B, Foo is aligned at the TopEnd, so it moves horizontally when coming // from A. scene(SceneB) { - Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.TopEnd)) } - } - - // In scene C, Foo is aligned at the BottomEnd, so it moves vertically when coming - // from B. - scene(SceneC) { - Box(Modifier.fillMaxSize()) { Foo(Modifier.align(Alignment.BottomEnd)) } + Box(Modifier.fillMaxSize()) { Foo(sizeInB, Modifier.align(Alignment.TopEnd)) } } } } // The offset of Foo when idle in A, B or C. val offsetInA = DpOffset.Zero - val offsetInB = DpOffset(layoutSize.width - fooSize.width, 0.dp) - val offsetInC = - DpOffset(layoutSize.width - fooSize.width, layoutSize.height - fooSize.height) + val offsetInB = DpOffset(layoutSize.width - sizeInB, 0.dp) + val offsetInC = DpOffset(layoutSize.width - sizeInC, layoutSize.height - sizeInC) // Initial state (idle in A). rule .onNode(isElement(TestElements.Foo, SceneA)) + .assertSizeIsEqualTo(sizeInA) .assertPositionInRootIsEqualTo(offsetInA.x, offsetInA.y) // Current transition is A => B at 50%. @@ -1091,9 +1144,11 @@ class ElementTest { onFinish = neverFinish(), ) val offsetInAToB = lerp(offsetInA, offsetInB, aToBProgress) + val sizeInAToB = lerp(sizeInA, sizeInB, aToBProgress) rule.runOnUiThread { state.startTransition(aToB, transitionKey = null) } rule .onNode(isElement(TestElements.Foo, SceneB)) + .assertSizeIsEqualTo(sizeInAToB) .assertPositionInRootIsEqualTo(offsetInAToB.x, offsetInAToB.y) // Start B => C at 0%. @@ -1108,39 +1163,66 @@ class ElementTest { ) rule.runOnUiThread { state.startTransition(bToC, transitionKey = null) } - // The offset interruption delta, which will be multiplied by the interruption progress then - // added to the current transition offset. - val interruptionDelta = offsetInAToB - offsetInB + // The interruption deltas, which will be multiplied by the interruption progress then added + // to the current transition offset and size. + val offsetInterruptionDelta = offsetInAToB - offsetInB + val sizeInterruptionDelta = sizeInAToB - sizeInB // Interruption progress is at 100% and bToC is at 0%, so Foo should be at the same offset - // as right before the interruption. + // and size as right before the interruption. rule - .onNode(isElement(TestElements.Foo, SceneC)) + .onNode(isElement(TestElements.Foo, SceneB)) .assertPositionInRootIsEqualTo(offsetInAToB.x, offsetInAToB.y) + .assertSizeIsEqualTo(sizeInAToB) // Move the transition forward at 30% and set the interruption progress to 50%. bToCProgress = 0.3f interruptionProgress = 0.5f val offsetInBToC = lerp(offsetInB, offsetInC, bToCProgress) + val sizeInBToC = lerp(sizeInB, sizeInC, bToCProgress) val offsetInBToCWithInterruption = offsetInBToC + DpOffset( - interruptionDelta.x * interruptionProgress, - interruptionDelta.y * interruptionProgress, + offsetInterruptionDelta.x * interruptionProgress, + offsetInterruptionDelta.y * interruptionProgress, ) + val sizeInBToCWithInterruption = sizeInBToC + sizeInterruptionDelta * interruptionProgress rule.waitForIdle() rule - .onNode(isElement(TestElements.Foo, SceneC)) + .onNode(isElement(TestElements.Foo, SceneB)) .assertPositionInRootIsEqualTo( offsetInBToCWithInterruption.x, offsetInBToCWithInterruption.y, ) + .assertSizeIsEqualTo(sizeInBToCWithInterruption) // Finish the transition and interruption. bToCProgress = 1f interruptionProgress = 0f rule - .onNode(isElement(TestElements.Foo, SceneC)) + .onNode(isElement(TestElements.Foo, SceneB)) .assertPositionInRootIsEqualTo(offsetInC.x, offsetInC.y) + .assertSizeIsEqualTo(sizeInC) + + // Manually finish the transition. + rule.runOnUiThread { + state.finishTransition(aToB, SceneB) + state.finishTransition(bToC, SceneC) + } + rule.waitForIdle() + assertThat(state.transitionState).isIdle() + + // The interruption values should be unspecified and deltas should be set to zero. + val foo = layoutImpl.elements.getValue(TestElements.Foo) + assertThat(foo.sceneStates.keys).containsExactly(SceneC) + val stateInC = foo.sceneStates.getValue(SceneC) + assertThat(stateInC.offsetBeforeInterruption).isEqualTo(Offset.Unspecified) + assertThat(stateInC.sizeBeforeInterruption).isEqualTo(Element.SizeUnspecified) + assertThat(stateInC.scaleBeforeInterruption).isEqualTo(Scale.Unspecified) + assertThat(stateInC.alphaBeforeInterruption).isEqualTo(Element.AlphaUnspecified) + assertThat(stateInC.offsetInterruptionDelta).isEqualTo(Offset.Zero) + assertThat(stateInC.sizeInterruptionDelta).isEqualTo(IntSize.Zero) + assertThat(stateInC.scaleInterruptionDelta).isEqualTo(Scale.Zero) + assertThat(stateInC.alphaInterruptionDelta).isEqualTo(0f) } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt new file mode 100644 index 000000000000..85d4165b4bf6 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/InterruptionHandlerTest.kt @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene + +import androidx.compose.animation.core.tween +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.subjects.assertThat +import com.android.compose.test.runMonotonicClockTest +import com.google.common.truth.Correspondence +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.launch +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class InterruptionHandlerTest { + @get:Rule val rule = createComposeRule() + + @Test + fun default() = runMonotonicClockTest { + val state = + MutableSceneTransitionLayoutState( + SceneA, + transitions { /* default interruption handler */}, + ) + + state.setTargetScene(SceneB, coroutineScope = this) + state.setTargetScene(SceneC, coroutineScope = this) + + assertThat(state.currentTransitions) + .comparingElementsUsing(FromToCurrentTriple) + .containsExactly( + // A to B. + Triple(SceneA, SceneB, SceneB), + + // B to C. + Triple(SceneB, SceneC, SceneC), + ) + .inOrder() + } + + @Test + fun chainingDisabled() = runMonotonicClockTest { + val state = + MutableSceneTransitionLayoutState( + SceneA, + transitions { + // Handler that animates from currentScene (default) but disables chaining. + interruptionHandler = + object : InterruptionHandler { + override fun onInterruption( + interrupted: TransitionState.Transition, + newTargetScene: SceneKey + ): InterruptionResult { + return InterruptionResult( + animateFrom = interrupted.currentScene, + chain = false, + ) + } + } + }, + ) + + state.setTargetScene(SceneB, coroutineScope = this) + state.setTargetScene(SceneC, coroutineScope = this) + + assertThat(state.currentTransitions) + .comparingElementsUsing(FromToCurrentTriple) + .containsExactly( + // B to C. + Triple(SceneB, SceneC, SceneC), + ) + .inOrder() + } + + @Test + fun animateFromOtherScene() = runMonotonicClockTest { + val duration = 500 + val state = + MutableSceneTransitionLayoutState( + SceneA, + transitions { + // Handler that animates from the scene that is not currentScene. + interruptionHandler = + object : InterruptionHandler { + override fun onInterruption( + interrupted: TransitionState.Transition, + newTargetScene: SceneKey + ): InterruptionResult { + return InterruptionResult( + animateFrom = + if (interrupted.currentScene == interrupted.toScene) { + interrupted.fromScene + } else { + interrupted.toScene + } + ) + } + } + + from(SceneA, to = SceneB) { spec = tween(duration) } + }, + ) + + // Animate to B and advance the transition a little bit so that progress > visibility + // threshold and that reversing from B back to A won't immediately snap to A. + state.setTargetScene(SceneB, coroutineScope = this) + testScheduler.advanceTimeBy(duration / 2L) + + state.setTargetScene(SceneC, coroutineScope = this) + + assertThat(state.currentTransitions) + .comparingElementsUsing(FromToCurrentTriple) + .containsExactly( + // Initial transition A to B. This transition will never be consumed by anyone given + // that it has the same (from, to) pair as the next transition. + Triple(SceneA, SceneB, SceneB), + + // Initial transition reversed, B back to A. + Triple(SceneA, SceneB, SceneA), + + // A to C. + Triple(SceneA, SceneC, SceneC), + ) + .inOrder() + } + + @Test + fun animateToFromScene() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) + + // Fake a transition from A to B that has a non 0 velocity. + val progressVelocity = 1f + val aToB = + transition( + from = SceneA, + to = SceneB, + current = { SceneB }, + // Progress must be > visibility threshold otherwise we will directly snap to A. + progress = { 0.5f }, + progressVelocity = { progressVelocity }, + onFinish = { launch {} }, + ) + state.startTransition(aToB, transitionKey = null) + + // Animate back to A. The previous transition is reversed, i.e. it has the same (from, to) + // pair, and its velocity is used when animating the progress back to 0. + val bToA = checkNotNull(state.setTargetScene(SceneA, coroutineScope = this)) + testScheduler.runCurrent() + assertThat(bToA).hasFromScene(SceneA) + assertThat(bToA).hasToScene(SceneB) + assertThat(bToA).hasCurrentScene(SceneA) + assertThat(bToA).hasProgressVelocity(progressVelocity) + } + + @Test + fun animateToToScene() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) + + // Fake a transition from A to B with current scene = A that has a non 0 velocity. + val progressVelocity = -1f + val aToB = + transition( + from = SceneA, + to = SceneB, + current = { SceneA }, + progressVelocity = { progressVelocity }, + onFinish = { launch {} }, + ) + state.startTransition(aToB, transitionKey = null) + + // Animate to B. The previous transition is reversed, i.e. it has the same (from, to) pair, + // and its velocity is used when animating the progress to 1. + val bToA = checkNotNull(state.setTargetScene(SceneB, coroutineScope = this)) + testScheduler.runCurrent() + assertThat(bToA).hasFromScene(SceneA) + assertThat(bToA).hasToScene(SceneB) + assertThat(bToA).hasCurrentScene(SceneB) + assertThat(bToA).hasProgressVelocity(progressVelocity) + } + + companion object { + val FromToCurrentTriple = + Correspondence.transforming( + { transition: TransitionState.Transition? -> + Triple(transition?.fromScene, transition?.toScene, transition?.currentScene) + }, + "(from, to, current) triple" + ) + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt index 224ffe29a1b8..9523896e5c00 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt @@ -43,6 +43,7 @@ import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.assertSizeIsEqualTo import com.google.common.truth.Truth.assertThat import org.junit.Rule @@ -157,8 +158,8 @@ class MovableElementTest { fromSceneZIndex: Float, toSceneZIndex: Float ): SceneKey { - assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) + assertThat(transition).hasFromScene(TestScenes.SceneA) + assertThat(transition).hasToScene(TestScenes.SceneB) assertThat(fromSceneZIndex).isEqualTo(0) assertThat(toSceneZIndex).isEqualTo(1) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index d8cf1c12989b..aa6d1130fc2a 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -17,7 +17,10 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.size import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -25,6 +28,9 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size +import androidx.compose.ui.input.pointer.AwaitPointerEventScope +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.test.junit4.createComposeRule @@ -32,6 +38,8 @@ import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performTouchInput import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.isActive import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -110,4 +118,232 @@ class MultiPointerDraggableTest { assertThat(dragged).isTrue() assertThat(stopped).isTrue() } + + @Test + fun handleDisappearingScrollableDuringAGesture() { + val size = 200f + val middle = Offset(size / 2f, size / 2f) + + var started = false + var dragged = false + var stopped = false + var consumedByScroll = false + var hasScrollable by mutableStateOf(true) + + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + Box( + Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() }) + .multiPointerDraggable( + orientation = Orientation.Vertical, + enabled = { true }, + startDragImmediately = { false }, + onDragStarted = { _, _, _ -> + started = true + object : DragController { + override fun onDrag(delta: Float) { + dragged = true + } + + override fun onStop(velocity: Float, canChangeScene: Boolean) { + stopped = true + } + } + }, + ) + ) { + if (hasScrollable) { + Box( + Modifier.scrollable( + // Consume all the vertical scroll gestures + rememberScrollableState( + consumeScrollDelta = { + consumedByScroll = true + it + } + ), + Orientation.Vertical + ) + .fillMaxSize() + ) + } + } + } + + fun startDraggingDown() { + rule.onRoot().performTouchInput { + down(middle) + moveBy(Offset(0f, touchSlop)) + } + } + + fun continueDraggingDown() { + rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) } + } + + fun releaseFinger() { + rule.onRoot().performTouchInput { up() } + } + + // Swipe down. This should intercepted by the scrollable modifier. + startDraggingDown() + assertThat(consumedByScroll).isTrue() + assertThat(started).isFalse() + assertThat(dragged).isFalse() + assertThat(stopped).isFalse() + + // Reset the scroll state for the test + consumedByScroll = false + + // Suddenly remove the scrollable container + hasScrollable = false + rule.waitForIdle() + + // Swipe down. This will be intercepted by multiPointerDraggable, it will wait touchSlop + // before consuming it. + continueDraggingDown() + assertThat(consumedByScroll).isFalse() + assertThat(started).isFalse() + assertThat(dragged).isFalse() + assertThat(stopped).isFalse() + + // Swipe down. This should both call onDragStarted() and onDragDelta(). + continueDraggingDown() + assertThat(consumedByScroll).isFalse() + assertThat(started).isTrue() + assertThat(dragged).isTrue() + assertThat(stopped).isFalse() + + rule.waitForIdle() + releaseFinger() + assertThat(stopped).isTrue() + } + + @Test + fun multiPointerWaitAConsumableEventInMainPass() { + val size = 200f + val middle = Offset(size / 2f, size / 2f) + + var started = false + var dragged = false + var stopped = false + + var childConsumesOnPass: PointerEventPass? = null + + suspend fun AwaitPointerEventScope.childPointerInputScope() { + awaitPointerEvent(PointerEventPass.Initial).also { initial -> + // Check unconsumed: it should be always true + assertThat(initial.changes.any { it.isConsumed }).isFalse() + + if (childConsumesOnPass == PointerEventPass.Initial) { + initial.changes.first().consume() + } + } + + awaitPointerEvent(PointerEventPass.Main).also { main -> + // Check unconsumed + if (childConsumesOnPass != PointerEventPass.Initial) { + assertThat(main.changes.any { it.isConsumed }).isFalse() + } + + if (childConsumesOnPass == PointerEventPass.Main) { + main.changes.first().consume() + } + } + } + + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + Box( + Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() }) + .multiPointerDraggable( + orientation = Orientation.Vertical, + enabled = { true }, + startDragImmediately = { false }, + onDragStarted = { _, _, _ -> + started = true + object : DragController { + override fun onDrag(delta: Float) { + dragged = true + } + + override fun onStop(velocity: Float, canChangeScene: Boolean) { + stopped = true + } + } + }, + ) + ) { + Box( + Modifier.pointerInput(Unit) { + coroutineScope { + awaitPointerEventScope { + while (isActive) { + childPointerInputScope() + } + } + } + } + .fillMaxSize() + ) + } + } + + fun startDraggingDown() { + rule.onRoot().performTouchInput { + down(middle) + moveBy(Offset(0f, touchSlop)) + } + } + + fun continueDraggingDown() { + rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) } + } + + childConsumesOnPass = PointerEventPass.Initial + + startDraggingDown() + assertThat(started).isFalse() + assertThat(dragged).isFalse() + assertThat(stopped).isFalse() + + continueDraggingDown() + assertThat(started).isFalse() + assertThat(dragged).isFalse() + assertThat(stopped).isFalse() + + childConsumesOnPass = PointerEventPass.Main + + continueDraggingDown() + assertThat(started).isFalse() + assertThat(dragged).isFalse() + assertThat(stopped).isFalse() + + continueDraggingDown() + assertThat(started).isFalse() + assertThat(dragged).isFalse() + assertThat(stopped).isFalse() + + childConsumesOnPass = null + + // Swipe down. This will be intercepted by multiPointerDraggable, it will wait touchSlop + // before consuming it. + continueDraggingDown() + assertThat(started).isFalse() + assertThat(dragged).isFalse() + assertThat(stopped).isFalse() + + // Swipe down. This should both call onDragStarted() and onDragDelta(). + continueDraggingDown() + assertThat(started).isTrue() + assertThat(dragged).isTrue() + assertThat(stopped).isFalse() + + childConsumesOnPass = PointerEventPass.Main + + continueDraggingDown() + assertThat(stopped).isTrue() + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt index 0f9b0249b93f..2a75e13066df 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ObservableTransitionStateTest.kt @@ -16,10 +16,18 @@ package com.android.compose.animation.scene +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshots.Snapshot import androidx.compose.ui.test.junit4.createComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -55,8 +63,8 @@ class ObservableTransitionStateTest { } rule.testTransition( - from = TestScenes.SceneA, - to = TestScenes.SceneB, + from = SceneA, + to = SceneB, transitionLayout = { currentScene, onChangeScene -> state = updateSceneTransitionLayoutState( @@ -66,34 +74,90 @@ class ObservableTransitionStateTest { ) SceneTransitionLayout(state = state) { - scene(TestScenes.SceneA) {} - scene(TestScenes.SceneB) {} + scene(SceneA) {} + scene(SceneB) {} } } ) { before { - assertThat(observableState()) - .isEqualTo(ObservableTransitionState.Idle(TestScenes.SceneA)) + assertThat(observableState()).isEqualTo(ObservableTransitionState.Idle(SceneA)) } at(0) { val state = observableState() assertThat(state).isInstanceOf(ObservableTransitionState.Transition::class.java) assertThat((state as ObservableTransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneA) - assertThat(state.toScene).isEqualTo(TestScenes.SceneB) + .isEqualTo(SceneA) + assertThat(state.toScene).isEqualTo(SceneB) assertThat(state.progress()).isEqualTo(0f) } at(TestTransitionDuration / 2) { val state = observableState() assertThat((state as ObservableTransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneA) - assertThat(state.toScene).isEqualTo(TestScenes.SceneB) + .isEqualTo(SceneA) + assertThat(state.toScene).isEqualTo(SceneB) assertThat(state.progress()).isEqualTo(0.5f) } after { - assertThat(observableState()) - .isEqualTo(ObservableTransitionState.Idle(TestScenes.SceneB)) + assertThat(observableState()).isEqualTo(ObservableTransitionState.Idle(SceneB)) } } } + + @Test + fun observableCurrentScene() = runTestWithSnapshots { + val state = + MutableSceneTransitionLayoutStateImpl( + initialScene = SceneA, + transitions = transitions {}, + ) + val observableCurrentScene = + state.observableTransitionState().flatMapLatest { it.currentScene() } + + // Collect observableCurrentScene into currentScene (unfortunately we can't use + // collectValues in this test target). + val currentScene = + object { + private var _value: SceneKey? = null + val value: SceneKey + get() { + runCurrent() + return _value ?: error("observableCurrentScene has no value") + } + + init { + backgroundScope.launch { observableCurrentScene.collect { _value = it } } + } + } + + assertThat(currentScene.value).isEqualTo(SceneA) + + // Start a transition to Scene B. + var transitionCurrentScene by mutableStateOf(SceneA) + val transition = + transition(from = SceneA, to = SceneB, current = { transitionCurrentScene }) + state.startTransition(transition, transitionKey = null) + assertThat(currentScene.value).isEqualTo(SceneA) + + // Change the transition current scene. + transitionCurrentScene = SceneB + assertThat(currentScene.value).isEqualTo(SceneB) + + transitionCurrentScene = SceneA + assertThat(currentScene.value).isEqualTo(SceneA) + } + + // See http://shortn/_hj4Mhikmos for inspiration. + private fun runTestWithSnapshots(testBody: suspend TestScope.() -> Unit) { + val globalWriteObserverHandle = + Snapshot.registerGlobalWriteObserver { + // This is normally done by the compose runtime. + Snapshot.sendApplyNotifications() + } + + try { + runTest(testBody = testBody) + } finally { + globalWriteObserverHandle.dispose() + } + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index 93e94f8f95a2..d2c8bd6928ee 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -25,6 +25,7 @@ import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.TestScenes.SceneD +import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.animation.scene.transition.link.StateLink import com.android.compose.test.runMonotonicClockTest import com.google.common.truth.Truth.assertThat @@ -322,8 +323,8 @@ class SceneTransitionLayoutStateTest { // Go back to A. state.setTargetScene(SceneA, coroutineScope = this) testScheduler.advanceUntilIdle() - assertThat(state.currentTransition).isNull() - assertThat(state.transitionState.currentScene).isEqualTo(SceneA) + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneA) // Specific transition from A to B. assertThat( @@ -477,23 +478,24 @@ class SceneTransitionLayoutStateTest { overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) } } ) - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasNoOverscrollSpec() // overscroll for SceneA is NOT defined progress.value = -0.1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() // scroll from SceneA to SceneB progress.value = 0.5f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() progress.value = 1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() // overscroll for SceneB is defined progress.value = 1.1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() - assertThat(state.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneB) + val overscrollSpec = assertThat(transition).hasOverscrollSpec() + assertThat(overscrollSpec.scene).isEqualTo(SceneB) } @Test @@ -507,23 +509,25 @@ class SceneTransitionLayoutStateTest { overscroll(SceneA, Orientation.Vertical) { fade(TestElements.Foo) } } ) - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasNoOverscrollSpec() // overscroll for SceneA is defined progress.value = -0.1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNotNull() - assertThat(state.currentTransition?.currentOverscrollSpec?.scene).isEqualTo(SceneA) + val overscrollSpec = assertThat(transition).hasOverscrollSpec() + assertThat(overscrollSpec.scene).isEqualTo(SceneA) // scroll from SceneA to SceneB progress.value = 0.5f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() progress.value = 1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() // overscroll for SceneB is NOT defined progress.value = 1.1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() } @Test @@ -534,22 +538,24 @@ class SceneTransitionLayoutStateTest { progress = { progress.value }, sceneTransitions = transitions {} ) - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasNoOverscrollSpec() // overscroll for SceneA is NOT defined progress.value = -0.1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() // scroll from SceneA to SceneB progress.value = 0.5f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() progress.value = 1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() // overscroll for SceneB is NOT defined progress.value = 1.1f - assertThat(state.currentTransition?.currentOverscrollSpec).isNull() + assertThat(transition).hasNoOverscrollSpec() } @Test @@ -629,4 +635,19 @@ class SceneTransitionLayoutStateTest { Log.setWtfHandler(originalHandler) } } + + @Test + fun snapToScene() = runMonotonicClockTest { + val state = MutableSceneTransitionLayoutState(SceneA) + + // Transition to B. + state.setTargetScene(SceneB, coroutineScope = this) + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasCurrentScene(SceneB) + + // Snap to C. + state.snapToScene(SceneC) + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneC) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 7836581c86e8..3751a229b690 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -51,6 +51,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.subjects.assertThat import com.android.compose.test.assertSizeIsEqualTo import com.android.compose.test.subjects.DpOffsetSubject import com.android.compose.test.subjects.assertThat @@ -75,12 +76,13 @@ class SceneTransitionLayoutTest { /** The content under test. */ @Composable - private fun TestContent() { + private fun TestContent(enableInterruptions: Boolean = true) { layoutState = updateSceneTransitionLayoutState( currentScene, { currentScene = it }, - EmptyTestTransitions + EmptyTestTransitions, + enableInterruptions = enableInterruptions, ) SceneTransitionLayout( @@ -147,34 +149,34 @@ class SceneTransitionLayoutTest { rule.onNodeWithText("SceneA").assertIsDisplayed() rule.onNodeWithText("SceneB").assertDoesNotExist() rule.onNodeWithText("SceneC").assertDoesNotExist() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // Change to scene B. Only that scene is displayed. currentScene = SceneB rule.onNodeWithText("SceneA").assertDoesNotExist() rule.onNodeWithText("SceneB").assertIsDisplayed() rule.onNodeWithText("SceneC").assertDoesNotExist() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneB) } @Test fun testBack() { rule.setContent { TestContent() } - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA) + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) rule.activity.onBackPressed() rule.waitForIdle() - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB) + assertThat(layoutState.transitionState).hasCurrentScene(SceneB) } @Test fun testTransitionState() { rule.setContent { TestContent() } - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // We will advance the clock manually. rule.mainClock.autoAdvance = false @@ -182,50 +184,43 @@ class SceneTransitionLayoutTest { // Change the current scene. Until composition is triggered, this won't change the layout // state. currentScene = SceneB - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // On the next frame, we will recompose because currentScene changed, which will start the // transition (i.e. it will change the transitionState to be a Transition) in a // LaunchedEffect. rule.mainClock.advanceTimeByFrame() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java) - val transition = layoutState.transitionState as TransitionState.Transition - assertThat(transition.fromScene).isEqualTo(SceneA) - assertThat(transition.toScene).isEqualTo(SceneB) - assertThat(transition.progress).isEqualTo(0f) + val transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneB) + assertThat(transition).hasProgress(0f) // Then, on the next frame, the animator we started gets its initial value and clock // starting time. We are now at progress = 0f. rule.mainClock.advanceTimeByFrame() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java) - assertThat((layoutState.transitionState as TransitionState.Transition).progress) - .isEqualTo(0f) + assertThat(transition).hasProgress(0f) // The test transition lasts 480ms. 240ms after the start of the transition, we are at // progress = 0.5f. rule.mainClock.advanceTimeBy(TestTransitionDuration / 2) - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java) - assertThat((layoutState.transitionState as TransitionState.Transition).progress) - .isEqualTo(0.5f) + assertThat(transition).hasProgress(0.5f) // (240-16) ms later, i.e. one frame before the transition is finished, we are at // progress=(480-16)/480. rule.mainClock.advanceTimeBy(TestTransitionDuration / 2 - 16) - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Transition::class.java) - assertThat((layoutState.transitionState as TransitionState.Transition).progress) - .isEqualTo((TestTransitionDuration - 16) / 480f) + assertThat(transition).hasProgress((TestTransitionDuration - 16) / 480f) // one frame (16ms) later, the transition is finished and we are in the idle state in scene // B. rule.mainClock.advanceTimeByFrame() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneB) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneB) } @Test fun testSharedElement() { - rule.setContent { TestContent() } + rule.setContent { TestContent(enableInterruptions = false) } // In scene A, the shared element SharedFoo() is at the top end of the layout and has a size // of 50.dp. @@ -261,8 +256,8 @@ class SceneTransitionLayoutTest { // 100.dp. We pause at the middle of the transition, so it should now be 75.dp given that we // use a linear interpolator. Foo was at (x = layoutSize - 50dp, y = 0) in SceneA and is // going to (x = 0, y = 0), so the offset should now be half what it was. - assertThat((layoutState.transitionState as TransitionState.Transition).progress) - .isEqualTo(0.5f) + var transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasProgress(0.5f) sharedFoo.assertWidthIsEqualTo(75.dp) sharedFoo.assertHeightIsEqualTo(75.dp) sharedFoo.assertPositionInRootIsEqualTo( @@ -290,8 +285,8 @@ class SceneTransitionLayoutTest { val expectedSize = 100.dp + (150.dp - 100.dp) * interpolatedProgress sharedFoo = rule.onNode(isElement(TestElements.Foo, SceneC)) - assertThat((layoutState.transitionState as TransitionState.Transition).progress) - .isEqualTo(interpolatedProgress) + transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasProgress(interpolatedProgress) sharedFoo.assertWidthIsEqualTo(expectedSize) sharedFoo.assertHeightIsEqualTo(expectedSize) sharedFoo.assertPositionInRootIsEqualTo(expectedLeft, expectedTop) @@ -305,16 +300,16 @@ class SceneTransitionLayoutTest { // Wait for the transition to C to finish. rule.mainClock.advanceTimeBy(TestTransitionDuration) - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneC) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Go back to scene A. This should happen instantly (once the animation started, i.e. after // 2 frames) given that we use a snap() animation spec. currentScene = SceneA rule.mainClock.advanceTimeByFrame() rule.mainClock.advanceTimeByFrame() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) } @Test @@ -384,7 +379,9 @@ class SceneTransitionLayoutTest { rule.mainClock.advanceTimeByFrame() rule.mainClock.advanceTimeBy(duration / 2) rule.waitForIdle() - assertThat(state.currentTransition?.progress).isEqualTo(0.5f) + + var transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasProgress(0.5f) // A and B are composed. rule.onNodeWithTag("aRoot").assertExists() @@ -396,7 +393,9 @@ class SceneTransitionLayoutTest { rule.mainClock.advanceTimeByFrame() rule.mainClock.advanceTimeByFrame() rule.waitForIdle() - assertThat(state.currentTransition?.progress).isEqualTo(0f) + + transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasProgress(0f) // A, B and C are composed. rule.onNodeWithTag("aRoot").assertExists() @@ -405,7 +404,7 @@ class SceneTransitionLayoutTest { // Let A => B finish. rule.mainClock.advanceTimeBy(duration / 2L) - assertThat(state.currentTransition?.progress).isEqualTo(0.5f) + assertThat(transition).hasProgress(0.5f) rule.waitForIdle() // B and C are composed. @@ -416,8 +415,8 @@ class SceneTransitionLayoutTest { // Let B => C finish. rule.mainClock.advanceTimeBy(duration / 2L) rule.mainClock.advanceTimeByFrame() - assertThat(state.currentTransition).isNull() rule.waitForIdle() + assertThat(state.transitionState).isIdle() // Only C is composed. rule.onNodeWithTag("aRoot").assertDoesNotExist() diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index f034c184b794..3a806a44be64 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -38,6 +38,9 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.android.compose.animation.scene.subjects.assertThat import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -65,9 +68,11 @@ class SwipeToSceneTest { @get:Rule val rule = createComposeRule() private fun layoutState( - initialScene: SceneKey = TestScenes.SceneA, + initialScene: SceneKey = SceneA, transitions: SceneTransitions = EmptyTestTransitions, - ) = MutableSceneTransitionLayoutState(initialScene, transitions) + ): MutableSceneTransitionLayoutState { + return rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene, transitions) } + } /** The content under test. */ @Composable @@ -80,22 +85,21 @@ class SwipeToSceneTest { modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName), ) { scene( - TestScenes.SceneA, + SceneA, userActions = if (swipesEnabled()) mapOf( - Swipe.Left to TestScenes.SceneB, + Swipe.Left to SceneB, Swipe.Down to TestScenes.SceneC, - Swipe.Up to TestScenes.SceneB, + Swipe.Up to SceneB, ) else emptyMap(), ) { Box(Modifier.fillMaxSize()) } scene( - TestScenes.SceneB, - userActions = - if (swipesEnabled()) mapOf(Swipe.Right to TestScenes.SceneA) else emptyMap(), + SceneB, + userActions = if (swipesEnabled()) mapOf(Swipe.Right to SceneA) else emptyMap(), ) { Box(Modifier.fillMaxSize()) } @@ -104,11 +108,10 @@ class SwipeToSceneTest { userActions = if (swipesEnabled()) mapOf( - Swipe.Down to TestScenes.SceneA, - Swipe(SwipeDirection.Down, pointerCount = 2) to TestScenes.SceneB, - Swipe(SwipeDirection.Right, fromSource = Edge.Left) to - TestScenes.SceneB, - Swipe(SwipeDirection.Down, fromSource = Edge.Top) to TestScenes.SceneB, + Swipe.Down to SceneA, + Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB, + Swipe(SwipeDirection.Right, fromSource = Edge.Left) to SceneB, + Swipe(SwipeDirection.Down, fromSource = Edge.Top) to SceneB, ) else emptyMap(), ) { @@ -129,8 +132,8 @@ class SwipeToSceneTest { TestContent(layoutState) } - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // Drag left (i.e. from right to left) by 55dp. We pick 55dp here because 56dp is the // positional threshold from which we commit the gesture. @@ -144,31 +147,27 @@ class SwipeToSceneTest { // We should be at a progress = 55dp / LayoutWidth given that we use the layout size in // the gesture axis as swipe distance. - var transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneA) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) - assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) - assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) - assertThat(transition.isInitiatedByUserInput).isTrue() + var transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneB) + assertThat(transition).hasCurrentScene(SceneA) + assertThat(transition).hasProgress(55.dp / LayoutWidth) + assertThat(transition).isInitiatedByUserInput() // Release the finger. We should now be animating back to A (currentScene = SceneA) given // that 55dp < positional threshold. rule.onRoot().performTouchInput { up() } - transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneA) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) - assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) - assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) - assertThat(transition.isInitiatedByUserInput).isTrue() + transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneB) + assertThat(transition).hasCurrentScene(SceneA) + assertThat(transition).hasProgress(55.dp / LayoutWidth) + assertThat(transition).isInitiatedByUserInput() // Wait for the animation to finish. We should now be in scene A. rule.waitForIdle() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // Now we do the same but vertically and with a drag distance of 56dp, which is >= // positional threshold. @@ -178,31 +177,27 @@ class SwipeToSceneTest { } // Drag is in progress, so currentScene = SceneA and progress = 56dp / LayoutHeight - transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneA) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) - assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) - assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) - assertThat(transition.isInitiatedByUserInput).isTrue() + transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasCurrentScene(SceneA) + assertThat(transition).hasProgress(56.dp / LayoutHeight) + assertThat(transition).isInitiatedByUserInput() // Release the finger. We should now be animating to C (currentScene = SceneC) given // that 56dp >= positional threshold. rule.onRoot().performTouchInput { up() } - transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneA) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) - assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC) - assertThat(transition.progress).isEqualTo(56.dp / LayoutHeight) - assertThat(transition.isInitiatedByUserInput).isTrue() + transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasCurrentScene(TestScenes.SceneC) + assertThat(transition).hasProgress(56.dp / LayoutHeight) + assertThat(transition).isInitiatedByUserInput() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) } @Test @@ -216,8 +211,8 @@ class SwipeToSceneTest { TestContent(layoutState) } - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // Swipe left (i.e. from right to left) using a velocity of 124 dp/s. We pick 124 dp/s here // because 125 dp/s is the velocity threshold from which we commit the gesture. We also use @@ -233,18 +228,16 @@ class SwipeToSceneTest { // We should be animating back to A (currentScene = SceneA) given that 124 dp/s < velocity // threshold. - var transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneA) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) - assertThat(transition.currentScene).isEqualTo(TestScenes.SceneA) - assertThat(transition.progress).isEqualTo(55.dp / LayoutWidth) + var transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneB) + assertThat(transition).hasCurrentScene(SceneA) + assertThat(transition).hasProgress(55.dp / LayoutWidth) // Wait for the animation to finish. We should now be in scene A. rule.waitForIdle() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // Now we do the same but vertically and with a swipe velocity of 126dp, which is > // velocity threshold. Note that in theory we could have used 125 dp (= velocity threshold) @@ -259,18 +252,16 @@ class SwipeToSceneTest { } // We should be animating to C (currentScene = SceneC). - transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneA) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneC) - assertThat(transition.currentScene).isEqualTo(TestScenes.SceneC) - assertThat(transition.progress).isEqualTo(55.dp / LayoutHeight) + transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasCurrentScene(TestScenes.SceneC) + assertThat(transition).hasProgress(55.dp / LayoutHeight) // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) } @Test @@ -286,8 +277,8 @@ class SwipeToSceneTest { TestContent(layoutState) } - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) // Swipe down with two fingers. rule.onRoot().performTouchInput { @@ -298,18 +289,16 @@ class SwipeToSceneTest { } // We are transitioning to B because we used 2 fingers. - val transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneC) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) + val transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only // swiped 10dp. rule.onRoot().performTouchInput { repeat(2) { i -> up(pointerId = i) } } rule.waitForIdle() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) } @Test @@ -325,8 +314,8 @@ class SwipeToSceneTest { TestContent(layoutState) } - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) // Swipe down from the top edge. rule.onRoot().performTouchInput { @@ -335,18 +324,16 @@ class SwipeToSceneTest { } // We are transitioning to B (and not A) because we started from the top edge. - var transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneC) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) + var transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only // swiped 10dp. rule.onRoot().performTouchInput { up() } rule.waitForIdle() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) // Swipe right from the left edge. rule.onRoot().performTouchInput { @@ -355,18 +342,16 @@ class SwipeToSceneTest { } // We are transitioning to B (and not A) because we started from the left edge. - transition = layoutState.transitionState - assertThat(transition).isInstanceOf(TransitionState.Transition::class.java) - assertThat((transition as TransitionState.Transition).fromScene) - .isEqualTo(TestScenes.SceneC) - assertThat(transition.toScene).isEqualTo(TestScenes.SceneB) + transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only // swiped 10dp. rule.onRoot().performTouchInput { up() } rule.waitForIdle() - assertThat(layoutState.transitionState).isInstanceOf(TransitionState.Idle::class.java) - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneC) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) } @Test @@ -380,7 +365,7 @@ class SwipeToSceneTest { layoutState( transitions = transitions { - from(TestScenes.SceneA, to = TestScenes.SceneB) { + from(SceneA, to = SceneB) { distance = FixedDistance(verticalSwipeDistance) } } @@ -395,12 +380,12 @@ class SwipeToSceneTest { modifier = Modifier.size(LayoutWidth, LayoutHeight) ) { scene( - TestScenes.SceneA, - userActions = mapOf(Swipe.Down to TestScenes.SceneB), + SceneA, + userActions = mapOf(Swipe.Down to SceneB), ) { Spacer(Modifier.fillMaxSize()) } - scene(TestScenes.SceneB) { Spacer(Modifier.fillMaxSize()) } + scene(SceneB) { Spacer(Modifier.fillMaxSize()) } } } @@ -413,9 +398,9 @@ class SwipeToSceneTest { } // We should be at 50% - val transition = layoutState.currentTransition + val transition = assertThat(layoutState.transitionState).isTransition() assertThat(transition).isNotNull() - assertThat(transition!!.progress).isEqualTo(0.5f) + assertThat(transition).hasProgress(0.5f) } @Test @@ -434,15 +419,14 @@ class SwipeToSceneTest { } // We should still correctly compute that we are swiping down to scene C. - var transition = layoutState.currentTransition - assertThat(transition).isNotNull() - assertThat(transition?.toScene).isEqualTo(TestScenes.SceneC) + var transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasToScene(TestScenes.SceneC) // Release the finger, animating back to scene A. rule.onRoot().performTouchInput { up() } rule.waitForIdle() - assertThat(layoutState.currentTransition).isNull() - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // Swipe up by exactly touchSlop, so that the drag overSlop is 0f. rule.onRoot().performTouchInput { @@ -451,15 +435,14 @@ class SwipeToSceneTest { } // We should still correctly compute that we are swiping up to scene B. - transition = layoutState.currentTransition - assertThat(transition).isNotNull() - assertThat(transition?.toScene).isEqualTo(TestScenes.SceneB) + transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasToScene(SceneB) // Release the finger, animating back to scene A. rule.onRoot().performTouchInput { up() } rule.waitForIdle() - assertThat(layoutState.currentTransition).isNull() - assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA) + assertThat(layoutState.transitionState).isIdle() + assertThat(layoutState.transitionState).hasCurrentScene(SceneA) // Swipe left by exactly touchSlop, so that the drag overSlop is 0f. rule.onRoot().performTouchInput { @@ -468,14 +451,13 @@ class SwipeToSceneTest { } // We should still correctly compute that we are swiping down to scene B. - transition = layoutState.currentTransition - assertThat(transition).isNotNull() - assertThat(transition?.toScene).isEqualTo(TestScenes.SceneB) + transition = assertThat(layoutState.transitionState).isTransition() + assertThat(transition).hasToScene(SceneB) } @Test fun swipeEnabledLater() { - val layoutState = MutableSceneTransitionLayoutState(TestScenes.SceneA) + val layoutState = layoutState() var swipesEnabled by mutableStateOf(false) var touchSlop = 0f rule.setContent { @@ -509,34 +491,32 @@ class SwipeToSceneTest { fun transitionKey() { val transitionkey = TransitionKey(debugName = "foo") val state = - MutableSceneTransitionLayoutState( - TestScenes.SceneA, + layoutState( + SceneA, transitions { - from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) } - from(TestScenes.SceneA, to = TestScenes.SceneB, key = transitionkey) { + from(SceneA, to = SceneB) { fade(TestElements.Foo) } + from(SceneA, to = SceneB, key = transitionkey) { fade(TestElements.Foo) fade(TestElements.Bar) } } ) - as MutableSceneTransitionLayoutStateImpl var touchSlop = 0f rule.setContent { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state, Modifier.size(LayoutWidth, LayoutHeight)) { scene( - TestScenes.SceneA, + SceneA, userActions = mapOf( - Swipe.Down to TestScenes.SceneB, - Swipe.Up to - UserActionResult(TestScenes.SceneB, transitionKey = transitionkey) + Swipe.Down to SceneB, + Swipe.Up to UserActionResult(SceneB, transitionKey = transitionkey) ) ) { Box(Modifier.fillMaxSize()) } - scene(TestScenes.SceneB) { Box(Modifier.fillMaxSize()) } + scene(SceneB) { Box(Modifier.fillMaxSize()) } } } @@ -546,12 +526,12 @@ class SwipeToSceneTest { moveBy(Offset(0f, touchSlop), delayMillis = 1_000) } - assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue() + assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue() assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(1) // Move the pointer up to swipe to scene B using the new transition. rule.onRoot().performTouchInput { moveBy(Offset(0f, -1.dp.toPx()), delayMillis = 1_000) } - assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue() + assertThat(state.isTransitioning(from = SceneA, to = SceneB)).isTrue() assertThat(state.currentTransition?.transformationSpec?.transformations).hasSize(2) } @@ -567,19 +547,17 @@ class SwipeToSceneTest { // the difference between the bottom of the scene and the bottom of the element, // so that we use the offset and size of the element as well as the size of the // scene. - val fooSize = TestElements.Foo.targetSize(TestScenes.SceneB) ?: return 0f - val fooOffset = TestElements.Foo.targetOffset(TestScenes.SceneB) ?: return 0f - val sceneSize = TestScenes.SceneB.targetSize() ?: return 0f + val fooSize = TestElements.Foo.targetSize(SceneB) ?: return 0f + val fooOffset = TestElements.Foo.targetOffset(SceneB) ?: return 0f + val sceneSize = SceneB.targetSize() ?: return 0f return sceneSize.height - fooOffset.y - fooSize.height } } val state = - MutableSceneTransitionLayoutState( - TestScenes.SceneA, - transitions { - from(TestScenes.SceneA, to = TestScenes.SceneB) { distance = swipeDistance } - } + layoutState( + SceneA, + transitions { from(SceneA, to = SceneB) { distance = swipeDistance } } ) val layoutSize = 200.dp @@ -591,10 +569,10 @@ class SwipeToSceneTest { touchSlop = LocalViewConfiguration.current.touchSlop SceneTransitionLayout(state, Modifier.size(layoutSize)) { - scene(TestScenes.SceneA, userActions = mapOf(Swipe.Up to TestScenes.SceneB)) { + scene(SceneA, userActions = mapOf(Swipe.Up to SceneB)) { Box(Modifier.fillMaxSize()) } - scene(TestScenes.SceneB) { + scene(SceneB) { Box(Modifier.fillMaxSize()) { Box(Modifier.offset(y = fooYOffset).element(TestElements.Foo).size(fooSize)) } @@ -611,7 +589,9 @@ class SwipeToSceneTest { } rule.waitForIdle() - assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue() - assertThat(state.currentTransition!!.progress).isWithin(0.01f).of(0.5f) + val transition = assertThat(state.transitionState).isTransition() + assertThat(transition).hasFromScene(SceneA) + assertThat(transition).hasToScene(SceneB) + assertThat(transition).hasProgress(0.5f, tolerance = 0.01f) } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt index c1218ae778ba..a609be48a225 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt @@ -27,7 +27,9 @@ import kotlinx.coroutines.test.TestScope fun transition( from: SceneKey, to: SceneKey, + current: () -> SceneKey = { from }, progress: () -> Float = { 0f }, + progressVelocity: () -> Float = { 0f }, interruptionProgress: () -> Float = { 100f }, isInitiatedByUserInput: Boolean = false, isUserInputOngoing: Boolean = false, @@ -37,9 +39,12 @@ fun transition( onFinish: ((TransitionState.Transition) -> Job)? = null, ): TransitionState.Transition { return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties { - override val currentScene: SceneKey = from + override val currentScene: SceneKey + get() = current() override val progress: Float get() = progress() + override val progressVelocity: Float + get() = progressVelocity() override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput override val isUserInputOngoing: Boolean = isUserInputOngoing diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt new file mode 100644 index 000000000000..348989218ce9 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/subjects/TransitionStateSubject.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.compose.animation.scene.subjects + +import com.android.compose.animation.scene.OverscrollSpec +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.TransitionState +import com.google.common.truth.Fact.simpleFact +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.google.common.truth.Subject.Factory +import com.google.common.truth.Truth + +/** Assert on a [TransitionState]. */ +fun assertThat(state: TransitionState): TransitionStateSubject { + return Truth.assertAbout(TransitionStateSubject.transitionStates()).that(state) +} + +/** Assert on a [TransitionState.Transition]. */ +fun assertThat(transitions: TransitionState.Transition): TransitionSubject { + return Truth.assertAbout(TransitionSubject.transitions()).that(transitions) +} + +class TransitionStateSubject +private constructor( + metadata: FailureMetadata, + private val actual: TransitionState, +) : Subject(metadata, actual) { + fun hasCurrentScene(sceneKey: SceneKey) { + check("currentScene").that(actual.currentScene).isEqualTo(sceneKey) + } + + fun isIdle(): TransitionState.Idle { + if (actual !is TransitionState.Idle) { + failWithActual(simpleFact("expected to be TransitionState.Idle")) + } + + return actual as TransitionState.Idle + } + + fun isTransition(): TransitionState.Transition { + if (actual !is TransitionState.Transition) { + failWithActual(simpleFact("expected to be TransitionState.Transition")) + } + + return actual as TransitionState.Transition + } + + companion object { + fun transitionStates() = Factory { metadata, actual: TransitionState -> + TransitionStateSubject(metadata, actual) + } + } +} + +class TransitionSubject +private constructor( + metadata: FailureMetadata, + private val actual: TransitionState.Transition, +) : Subject(metadata, actual) { + fun hasCurrentScene(sceneKey: SceneKey) { + check("currentScene").that(actual.currentScene).isEqualTo(sceneKey) + } + + fun hasFromScene(sceneKey: SceneKey) { + check("fromScene").that(actual.fromScene).isEqualTo(sceneKey) + } + + fun hasToScene(sceneKey: SceneKey) { + check("toScene").that(actual.toScene).isEqualTo(sceneKey) + } + + fun hasProgress(progress: Float, tolerance: Float = 0f) { + check("progress").that(actual.progress).isWithin(tolerance).of(progress) + } + + fun hasProgressVelocity(progressVelocity: Float, tolerance: Float = 0f) { + check("progressVelocity") + .that(actual.progressVelocity) + .isWithin(tolerance) + .of(progressVelocity) + } + + fun isInitiatedByUserInput() { + check("isInitiatedByUserInput").that(actual.isInitiatedByUserInput).isTrue() + } + + fun hasIsUserInputOngoing(isUserInputOngoing: Boolean) { + check("isUserInputOngoing").that(actual.isUserInputOngoing).isEqualTo(isUserInputOngoing) + } + + fun hasOverscrollSpec(): OverscrollSpec { + check("currentOverscrollSpec").that(actual.currentOverscrollSpec).isNotNull() + return actual.currentOverscrollSpec!! + } + + fun hasNoOverscrollSpec() { + check("currentOverscrollSpec").that(actual.currentOverscrollSpec).isNull() + } + + fun hasBouncingScene(scene: SceneKey) { + if (actual !is TransitionState.HasOverscrollProperties) { + failWithActual(simpleFact("expected to be TransitionState.HasOverscrollProperties")) + } + + check("bouncingScene") + .that((actual as TransitionState.HasOverscrollProperties).bouncingScene) + .isEqualTo(scene) + } + + companion object { + fun transitions() = Factory { metadata, actual: TransitionState.Transition -> + TransitionSubject(metadata, actual) + } + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt index e555a01d42fd..7b992124d836 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/AnchoredSizeTest.kt @@ -20,50 +20,48 @@ import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.TestElements -import com.android.compose.animation.scene.testTransition -import com.android.compose.test.assertSizeIsEqualTo +import com.android.compose.animation.scene.TransitionBuilder +import com.android.compose.animation.scene.TransitionRecordingSpec +import com.android.compose.animation.scene.featureOfElement +import com.android.compose.animation.scene.recordTransition import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import platform.test.motion.compose.ComposeFeatureCaptures +import platform.test.motion.compose.createComposeMotionTestRule +import platform.test.motion.testing.createGoldenPathManager @RunWith(AndroidJUnit4::class) class AnchoredSizeTest { - @get:Rule val rule = createComposeRule() + private val goldenPaths = + createGoldenPathManager("frameworks/base/packages/SystemUI/compose/scene/tests/goldens") + + @get:Rule val motionRule = createComposeMotionTestRule(goldenPaths) @Test fun testAnchoredSizeEnter() { - rule.testTransition( + assertBarSizeMatchesGolden( fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) }, toSceneContent = { Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo)) Box(Modifier.size(200.dp, 60.dp).element(TestElements.Bar)) }, transition = { - // Scale during 4 frames. spec = tween(16 * 4, easing = LinearEasing) anchoredSize(TestElements.Bar, TestElements.Foo) - }, - ) { - // Bar is entering. It starts at the same size as Foo in scene A in and scales to its - // final size in scene B. - before { onElement(TestElements.Bar).assertDoesNotExist() } - at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) } - at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 90.dp) } - at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 80.dp) } - at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 70.dp) } - at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } - after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } - } + } + ) } @Test fun testAnchoredSizeExit() { - rule.testTransition( + assertBarSizeMatchesGolden( fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) Box(Modifier.size(100.dp, 100.dp).element(TestElements.Bar)) @@ -73,22 +71,13 @@ class AnchoredSizeTest { // Scale during 4 frames. spec = tween(16 * 4, easing = LinearEasing) anchoredSize(TestElements.Bar, TestElements.Foo) - }, - ) { - // Bar is leaving. It starts at 100dp x 100dp in scene A and is scaled to 200dp x 60dp, - // the size of Foo in scene B. - before { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) } - at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 100.dp) } - at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 90.dp) } - at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 80.dp) } - at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 70.dp) } - after { onElement(TestElements.Bar).assertDoesNotExist() } - } + } + ) } @Test fun testAnchoredWidthOnly() { - rule.testTransition( + assertBarSizeMatchesGolden( fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) }, toSceneContent = { Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo)) @@ -98,20 +87,12 @@ class AnchoredSizeTest { spec = tween(16 * 4, easing = LinearEasing) anchoredSize(TestElements.Bar, TestElements.Foo, anchorHeight = false) }, - ) { - before { onElement(TestElements.Bar).assertDoesNotExist() } - at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(100.dp, 60.dp) } - at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(125.dp, 60.dp) } - at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(150.dp, 60.dp) } - at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(175.dp, 60.dp) } - at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } - after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } - } + ) } @Test fun testAnchoredHeightOnly() { - rule.testTransition( + assertBarSizeMatchesGolden( fromSceneContent = { Box(Modifier.size(100.dp, 100.dp).element(TestElements.Foo)) }, toSceneContent = { Box(Modifier.size(50.dp, 50.dp).element(TestElements.Foo)) @@ -120,15 +101,23 @@ class AnchoredSizeTest { transition = { spec = tween(16 * 4, easing = LinearEasing) anchoredSize(TestElements.Bar, TestElements.Foo, anchorWidth = false) - }, - ) { - before { onElement(TestElements.Bar).assertDoesNotExist() } - at(0) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 100.dp) } - at(16) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 90.dp) } - at(32) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 80.dp) } - at(48) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 70.dp) } - at(64) { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } - after { onElement(TestElements.Bar).assertSizeIsEqualTo(200.dp, 60.dp) } - } + } + ) + } + + private fun assertBarSizeMatchesGolden( + fromSceneContent: @Composable SceneScope.() -> Unit, + toSceneContent: @Composable SceneScope.() -> Unit, + transition: TransitionBuilder.() -> Unit, + ) { + val recordingSpec = + TransitionRecordingSpec(recordAfter = true) { + featureOfElement(TestElements.Bar, ComposeFeatureCaptures.dpSize) + } + + val motion = + motionRule.recordTransition(fromSceneContent, toSceneContent, transition, recordingSpec) + + motionRule.assertThat(motion).timeSeriesMatchesGolden() } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt index fbd1b512c50a..bca710f52c3f 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/test/SizeAssertions.kt @@ -21,7 +21,11 @@ import androidx.compose.ui.test.assertHeightIsEqualTo import androidx.compose.ui.test.assertWidthIsEqualTo import androidx.compose.ui.unit.Dp -fun SemanticsNodeInteraction.assertSizeIsEqualTo(expectedWidth: Dp, expectedHeight: Dp) { +fun SemanticsNodeInteraction.assertSizeIsEqualTo( + expectedWidth: Dp, + expectedHeight: Dp = expectedWidth, +): SemanticsNodeInteraction { assertWidthIsEqualTo(expectedWidth) assertHeightIsEqualTo(expectedHeight) + return this } diff --git a/packages/SystemUI/compose/scene/tests/utils/Android.bp b/packages/SystemUI/compose/scene/tests/utils/Android.bp index 9089e6a4b4b6..292efa085364 100644 --- a/packages/SystemUI/compose/scene/tests/utils/Android.bp +++ b/packages/SystemUI/compose/scene/tests/utils/Android.bp @@ -32,6 +32,7 @@ android_library { static_libs: [ "PlatformComposeSceneTransitionLayout", + "PlatformMotionTestingCompose", "androidx.compose.runtime_runtime", "androidx.compose.ui_ui-test-junit4", ], diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt index 2d71a6e50ac2..6724851dbec5 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt @@ -17,12 +17,24 @@ package com.android.compose.animation.scene import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.junit4.ComposeContentTestRule +import platform.test.motion.MotionTestRule +import platform.test.motion.RecordedMotion +import platform.test.motion.compose.ComposeRecordingSpec +import platform.test.motion.compose.ComposeToolkit +import platform.test.motion.compose.MotionControl +import platform.test.motion.compose.feature +import platform.test.motion.compose.recordMotion +import platform.test.motion.golden.FeatureCapture +import platform.test.motion.golden.TimeSeriesCaptureScope @DslMarker annotation class TransitionTestDsl @@ -100,6 +112,66 @@ fun ComposeContentTestRule.testTransition( ) } +data class TransitionRecordingSpec( + val recordBefore: Boolean = true, + val recordAfter: Boolean = true, + val timeSeriesCapture: TimeSeriesCaptureScope<SemanticsNodeInteractionsProvider>.() -> Unit +) + +/** Captures the feature using [capture] on the [element]. */ +fun TimeSeriesCaptureScope<SemanticsNodeInteractionsProvider>.featureOfElement( + element: ElementKey, + capture: FeatureCapture<SemanticsNode, *>, + name: String = "${element.debugName}_${capture.name}" +) { + feature(isElement(element), capture, name) +} + +/** Records the transition between two scenes of [transitionLayout][SceneTransitionLayout]. */ +fun MotionTestRule<ComposeToolkit>.recordTransition( + fromSceneContent: @Composable SceneScope.() -> Unit, + toSceneContent: @Composable SceneScope.() -> Unit, + transition: TransitionBuilder.() -> Unit, + recordingSpec: TransitionRecordingSpec, + layoutModifier: Modifier = Modifier, + fromScene: SceneKey = TestScenes.SceneA, + toScene: SceneKey = TestScenes.SceneB, +): RecordedMotion { + val state = + toolkit.composeContentTestRule.runOnUiThread { + MutableSceneTransitionLayoutState( + fromScene, + transitions { from(fromScene, to = toScene, builder = transition) } + ) + } + + return recordMotion( + content = { play -> + LaunchedEffect(play) { + if (play) { + state.setTargetScene(toScene, coroutineScope = this) + } + } + + SceneTransitionLayout( + state, + layoutModifier, + ) { + scene(fromScene, content = fromSceneContent) + scene(toScene, content = toSceneContent) + } + }, + ComposeRecordingSpec( + MotionControl(delayRecording = { awaitCondition { state.isTransitioning() } }) { + awaitCondition { !state.isTransitioning() } + }, + recordBefore = recordingSpec.recordBefore, + recordAfter = recordingSpec.recordAfter, + timeSeriesCapture = recordingSpec.timeSeriesCapture + ) + ) +} + /** * Test the transition between two scenes of [transitionLayout][SceneTransitionLayout] at different * points in time. 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/flag_check.py b/packages/SystemUI/flag_check.py index bac3553e7498..95a25c58bc67 100755 --- a/packages/SystemUI/flag_check.py +++ b/packages/SystemUI/flag_check.py @@ -12,19 +12,20 @@ following case-sensitive regex: %s The Flag: stanza is regex matched and should describe whether your change is behind a flag or flags. - -As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the -flag in addition to its state (ENABLED|DISABLED|DEVELOPMENT|STAGING|TEAMFOOD|TRUNKFOOD|NEXTFOOD). +As a CL author, you'll have a consistent place to describe the risk of the proposed change by explicitly calling out the name of the flag. +For legacy flags use EXEMPT with your flag name. Some examples below: -Flag: NONE -Flag: NA -Flag: LEGACY ENABLE_ONE_SEARCH DISABLED -Flag: ACONFIG com.android.launcher3.enable_twoline_allapps DEVELOPMENT -Flag: ACONFIG com.android.launcher3.enable_twoline_allapps TRUNKFOOD +Flag: NONE Repohook Update +Flag: TEST_ONLY +Flag: EXEMPT resource only update +Flag: EXEMPT bugfix +Flag: EXEMPT refactor +Flag: com.android.launcher3.enable_twoline_allapps +Flag: com.google.android.apps.nexuslauncher.zero_state_web_data_loader -Check the git history for more examples. It's a regex matched field. +Check the git history for more examples. It's a regex matched field. See go/android-flag-directive for more details on various formats. """ def main(): @@ -63,28 +64,31 @@ def main(): return field = 'Flag' - none = '(NONE|NA|N\/A)' # NONE|NA|N/A - - typeExpression = '\s*(LEGACY|ACONFIG)' # [type:LEGACY|ACONFIG] + none = 'NONE' + testOnly = 'TEST_ONLY' + docsOnly = 'DOCS_ONLY' + exempt = 'EXEMPT' + justification = '<justification>' - # legacyFlagName contains only uppercase alphabets with '_' - Ex: ENABLE_ONE_SEARCH - # Aconfig Flag name format = "packageName"."flagName" + # Aconfig Flag name format = <packageName>.<flagName> # package name - Contains only lowercase alphabets + digits + '.' - Ex: com.android.launcher3 - # For now alphabets, digits, "_", "." characters are allowed in flag name and not adding stricter format check. + # For now alphabets, digits, "_", "." characters are allowed in flag name. + # Checks if there is "one dot" between packageName and flagName and not adding stricter format check #common_typos_disable - flagName = '([a-zA-z0-9_.])+' + flagName = '([a-zA-Z0-9.]+)([.]+)([a-zA-Z0-9_.]+)' - #[state:ENABLED|DISABLED|DEVELOPMENT|TEAM*(TEAMFOOD)|STAGING|TRUNK*(TRUNK_STAGING, TRUNK_FOOD)|NEXT*(NEXTFOOD)] - stateExpression = '\s*(ENABLED|DISABLED|DEVELOPMENT|TEAM[a-zA-z]*|STAGING|TRUNK[a-zA-z]*|NEXT[a-zA-z]*)' + # None and Exempt needs justification + exemptRegex = fr'{exempt}\s*[a-zA-Z]+' + noneRegex = fr'{none}\s*[a-zA-Z]+' #common_typos_enable - readableRegexMsg = '\n\tFlag: (NONE|NA)\n\tFlag: LEGACY|ACONFIG FlagName|packageName.flagName ENABLED|DISABLED|DEVELOPMENT|TEAMFOOD|STAGING|TRUNKFOOD|NEXTFOOD' + readableRegexMsg = '\n\tFlag: '+none+' '+justification+'\n\tFlag: <packageName>.<flagName>\n\tFlag: ' +exempt+' '+justification+'\n\tFlag: '+testOnly+'\n\tFlag: '+docsOnly flagRegex = fr'^{field}: .*$' check_flag = re.compile(flagRegex) #Flag: # Ignore case for flag name format. - flagNameRegex = fr'(?i)^{field}:\s*({none}|{typeExpression}\s*{flagName}\s*{stateExpression})\s*' + flagNameRegex = fr'(?i)^{field}:\s*({noneRegex}|{flagName}|{testOnly}|{docsOnly}|{exemptRegex})\s*' check_flagName = re.compile(flagNameRegex) #Flag: <flag name format> flagError = False diff --git a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt index 47a00f408460..624f18d53a70 100644 --- a/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt +++ b/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt @@ -133,7 +133,7 @@ class ColorScheme( @ColorInt seed: Int, darkTheme: Boolean, style: Style - ) : this(seed, darkTheme, style, 0.5) + ) : this(seed, darkTheme, style, 0.0) @JvmOverloads constructor( diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 3554b8fbedd0..6c3f3c181d45 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -17,8 +17,11 @@ package com.android.keyguard +import android.app.admin.DevicePolicyManager +import android.app.admin.flags.Flags as DevicePolicyFlags import android.content.res.Configuration import android.media.AudioManager +import android.platform.test.annotations.EnableFlags import android.telephony.TelephonyManager import android.testing.TestableLooper.RunWithLooper import android.testing.TestableResources @@ -148,6 +151,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var postureController: DevicePostureController + @Mock private lateinit var devicePolicyManager: DevicePolicyManager @Captor private lateinit var swipeListenerArgumentCaptor: @@ -273,6 +277,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { mSelectedUserInteractor, deviceProvisionedController, faceAuthAccessibilityDelegate, + devicePolicyManager, keyguardTransitionInteractor, { primaryBouncerInteractor }, ) { @@ -795,6 +800,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { ObservableTransitionState.Transition( Scenes.Lockscreen, Scenes.Bouncer, + flowOf(Scenes.Bouncer), flowOf(.5f), false, isUserInputOngoing = flowOf(false), @@ -818,6 +824,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { ObservableTransitionState.Transition( Scenes.Bouncer, Scenes.Gone, + flowOf(Scenes.Gone), flowOf(.5f), false, isUserInputOngoing = flowOf(false), @@ -837,6 +844,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { ObservableTransitionState.Transition( Scenes.Gone, Scenes.Bouncer, + flowOf(Scenes.Bouncer), flowOf(.5f), false, isUserInputOngoing = flowOf(false), @@ -858,6 +866,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { ObservableTransitionState.Transition( Scenes.Bouncer, Scenes.Gone, + flowOf(Scenes.Gone), flowOf(.5f), false, isUserInputOngoing = flowOf(false), @@ -876,6 +885,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { ObservableTransitionState.Transition( Scenes.Gone, Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), flowOf(.5f), false, isUserInputOngoing = flowOf(false), @@ -896,6 +906,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { ObservableTransitionState.Transition( Scenes.Lockscreen, Scenes.Gone, + flowOf(Scenes.Gone), flowOf(.5f), false, isUserInputOngoing = flowOf(false), @@ -928,6 +939,45 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any()) } + @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) + fun showAlmostAtWipeDialog_calledOnMainUser_setsCorrectUserType() { + val mainUserId = 10 + + underTest.showMessageForFailedUnlockAttempt( + /* userId = */ mainUserId, + /* expiringUserId = */ mainUserId, + /* mainUserId = */ mainUserId, + /* remainingBeforeWipe = */ 1, + /* failedAttempts = */ 1 + ) + + verify(view) + .showAlmostAtWipeDialog(any(), any(), eq(KeyguardSecurityContainer.USER_TYPE_PRIMARY)) + } + + @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) + fun showAlmostAtWipeDialog_calledOnNonMainUser_setsCorrectUserType() { + val secondaryUserId = 10 + val mainUserId = 0 + + underTest.showMessageForFailedUnlockAttempt( + /* userId = */ secondaryUserId, + /* expiringUserId = */ secondaryUserId, + /* mainUserId = */ mainUserId, + /* remainingBeforeWipe = */ 1, + /* failedAttempts = */ 1 + ) + + verify(view) + .showAlmostAtWipeDialog( + any(), + any(), + eq(KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER) + ) + } + private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener get() { underTest.onViewAttached() 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..5757f67cc1dd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/NightDisplayRepositoryTest.kt @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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, + ) + + @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 { + 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!!.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) + enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser) + + 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) + enrollInForcedNightDisplayAutoMode(INITIALLY_FORCE_AUTO_MODE, testUser) + + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + assertThat(lastState!!.autoMode).isEqualTo(ColorDisplayManager.AUTO_MODE_TWILIGHT) + } + + /** + * When the value of the raw auto mode is missing the call to nightDisplayState should not crash + */ + @Test + fun nightDisplayState_whenAutoModeSettingIsNotInitialized_loadsDataWithoutException() = + scope.runTest { + // only auto mode_available is set, and the raw auto_mode has nothing set + globalSettings.putString( + Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE, + NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE + ) + + val lastState by collectLastValue(underTest.nightDisplayState(testUser)) + runCurrent() + + assertThat(lastState!!.shouldForceAutoMode).isTrue() + } + + @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/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt new file mode 100644 index 000000000000..c0d481c6e659 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryImplTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.os.UserHandle +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.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +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.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@android.platform.test.annotations.EnabledOnRavenwood +class OneHandedModeRepositoryImplTest : SysuiTestCase() { + + private val testUser1 = UserHandle.of(1)!! + private val testUser2 = UserHandle.of(2)!! + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val settings: FakeSettings = FakeSettings() + + private val underTest: OneHandedModeRepository = + OneHandedModeRepositoryImpl( + testDispatcher, + scope.backgroundScope, + settings, + ) + + @Test + fun isEnabled_settingNotInitialized_returnsFalseByDefault() = + scope.runTest { + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) + + runCurrent() + + assertThat(actualValue).isFalse() + } + + @Test + fun isEnabled_initiallyGetsSettingsValue() = + scope.runTest { + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) + + settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) + runCurrent() + + assertThat(actualValue).isTrue() + } + + @Test + fun isEnabled_settingUpdated_valueUpdated() = + scope.runTest { + val actualValue by collectLastValue(underTest.isEnabled(testUser1)) + runCurrent() + assertThat(actualValue).isFalse() + + settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) + runCurrent() + + assertThat(actualValue).isTrue() + runCurrent() + + settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) + runCurrent() + assertThat(actualValue).isFalse() + } + + @Test + fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() = + scope.runTest { + val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1)) + val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2)) + + settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier) + settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier) + runCurrent() + assertThat(lastValueUser1).isFalse() + assertThat(lastValueUser2).isFalse() + + settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier) + runCurrent() + assertThat(lastValueUser1).isTrue() + assertThat(lastValueUser2).isFalse() + } + + @Test + fun setEnabled() = + scope.runTest { + val success = underTest.setIsEnabled(true, testUser1) + runCurrent() + assertThat(success).isTrue() + + val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier) + assertThat(actualValue).isEqualTo(ENABLED) + } + + @Test + fun setDisabled() = + scope.runTest { + val success = underTest.setIsEnabled(false, testUser1) + runCurrent() + assertThat(success).isTrue() + + val actualValue = settings.getIntForUser(SETTING_NAME, testUser1.identifier) + assertThat(actualValue).isEqualTo(DISABLED) + } + + companion object { + private const val SETTING_NAME = Settings.Secure.ONE_HANDED_MODE_ENABLED + private const val DISABLED = 0 + private const val ENABLED = 1 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java index 11a42413c4ff..27bffd0818e7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.java @@ -18,10 +18,8 @@ package com.android.systemui.ambient.touch; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.view.GestureDetector; import android.view.MotionEvent; @@ -30,6 +28,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.statusbar.phone.CentralSurfaces; @@ -37,7 +36,6 @@ 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; import org.mockito.MockitoAnnotations; @@ -51,89 +49,66 @@ public class ShadeTouchHandlerTest extends SysuiTestCase { CentralSurfaces mCentralSurfaces; @Mock + ShadeViewController mShadeViewController; + + @Mock TouchHandler.TouchSession mTouchSession; ShadeTouchHandler mTouchHandler; - @Captor - ArgumentCaptor<GestureDetector.OnGestureListener> mGestureListenerCaptor; - @Captor - ArgumentCaptor<InputChannelCompat.InputEventListener> mInputListenerCaptor; - private static final int TOUCH_HEIGHT = 20; @Before public void setup() { MockitoAnnotations.initMocks(this); - - mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), TOUCH_HEIGHT); - } - - // Verifies that a swipe down in the gesture region is captured by the shade touch handler. - @Test - public void testSwipeDown_captured() { - final boolean captured = swipe(Direction.DOWN); - - assertThat(captured).isTrue(); + mTouchHandler = new ShadeTouchHandler(Optional.of(mCentralSurfaces), mShadeViewController, + TOUCH_HEIGHT); } - // Verifies that a swipe in the upward direction is not catpured. + /** + * Verify that touches aren't handled when the bouncer is showing. + */ @Test - public void testSwipeUp_notCaptured() { - final boolean captured = swipe(Direction.UP); - - // Motion events not captured as the swipe is going in the wrong direction. - assertThat(captured).isFalse(); + public void testInactiveOnBouncer() { + when(mCentralSurfaces.isBouncerShowing()).thenReturn(true); + mTouchHandler.onSessionStart(mTouchSession); + verify(mTouchSession).pop(); } - // Verifies that a swipe down forwards captured touches to the shade window for handling. + /** + * Make sure {@link ShadeTouchHandler} + */ @Test - public void testSwipeDown_sentToShadeWindow() { - swipe(Direction.DOWN); + public void testTouchPilferingOnScroll() { + final MotionEvent motionEvent1 = Mockito.mock(MotionEvent.class); + final MotionEvent motionEvent2 = Mockito.mock(MotionEvent.class); - // Both motion events are sent for the shade window to process. - verify(mCentralSurfaces, times(2)).handleExternalShadeWindowTouch(any()); - } + final ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerArgumentCaptor = + ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); - // Verifies that a swipe down is not forwarded to the shade window. - @Test - public void testSwipeUp_touchesNotSent() { - swipe(Direction.UP); + mTouchHandler.onSessionStart(mTouchSession); + verify(mTouchSession).registerGestureListener(gestureListenerArgumentCaptor.capture()); - // Motion events are not sent for the shade window to process as the swipe is going in the - // wrong direction. - verify(mCentralSurfaces, never()).handleExternalShadeWindowTouch(any()); + assertThat(gestureListenerArgumentCaptor.getValue() + .onScroll(motionEvent1, motionEvent2, 1, 1)) + .isTrue(); } /** - * Simulates a swipe in the given direction and returns true if the touch was intercepted by the - * touch handler's gesture listener. - * <p> - * Swipe down starts from a Y coordinate of 0 and goes downward. Swipe up starts from the edge - * of the gesture region, {@link #TOUCH_HEIGHT}, and goes upward to 0. + * Ensure touches are propagated to the {@link ShadeViewController}. */ - private boolean swipe(Direction direction) { - Mockito.clearInvocations(mTouchSession); - mTouchHandler.onSessionStart(mTouchSession); - - verify(mTouchSession).registerGestureListener(mGestureListenerCaptor.capture()); - verify(mTouchSession).registerInputListener(mInputListenerCaptor.capture()); - - final float startY = direction == Direction.UP ? TOUCH_HEIGHT : 0; - final float endY = direction == Direction.UP ? 0 : TOUCH_HEIGHT; + @Test + public void testEventPropagation() { + final MotionEvent motionEvent = Mockito.mock(MotionEvent.class); - // Send touches to the input and gesture listener. - final MotionEvent event1 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, startY, 0); - final MotionEvent event2 = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, endY, 0); - mInputListenerCaptor.getValue().onInputEvent(event1); - mInputListenerCaptor.getValue().onInputEvent(event2); - final boolean captured = mGestureListenerCaptor.getValue().onScroll(event1, event2, 0, - startY - endY); + final ArgumentCaptor<InputChannelCompat.InputEventListener> + inputEventListenerArgumentCaptor = + ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class); - return captured; + mTouchHandler.onSessionStart(mTouchSession); + verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture()); + inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent); + verify(mShadeViewController).handleExternalTouch(motionEvent); } - private enum Direction { - DOWN, UP, - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 81878aaf4a18..0c5e726e17aa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -17,6 +17,8 @@ package com.android.systemui.authentication.domain.interactor import android.app.admin.DevicePolicyManager +import android.app.admin.flags.Flags as DevicePolicyFlags +import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils @@ -32,6 +34,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.user.data.repository.fakeUserRepository import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -410,12 +414,16 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(DevicePolicyFlags.FLAG_HEADLESS_SINGLE_USER_FIXES) fun upcomingWipe() = testScope.runTest { val upcomingWipe by collectLastValue(underTest.upcomingWipe) kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) val correctPin = FakeAuthenticationRepository.DEFAULT_PIN val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 } + kosmos.fakeUserRepository.asMainUser() + kosmos.fakeAuthenticationRepository.profileWithMinFailedUnlockAttemptsForWipe = + FakeUserRepository.MAIN_USER_ID underTest.authenticate(correctPin) assertThat(upcomingWipe).isNull() 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/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index ecfcc90982c0..a5acf724dcff 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -66,15 +66,12 @@ class BouncerViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val authenticationInteractor by lazy { kosmos.authenticationInteractor } - private val bouncerInteractor by lazy { kosmos.bouncerInteractor } - private val sceneContainerStartable = kosmos.sceneContainerStartable private lateinit var underTest: BouncerViewModel @Before fun setUp() { - sceneContainerStartable.start() + kosmos.sceneContainerStartable.start() underTest = kosmos.bouncerViewModel } @@ -164,11 +161,11 @@ class BouncerViewModelTest : SysuiTestCase() { assertThat(isInputEnabled).isTrue() repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { - bouncerInteractor.authenticate(WRONG_PIN) + kosmos.bouncerInteractor.authenticate(WRONG_PIN) } assertThat(isInputEnabled).isFalse() - val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0 + val lockoutEndMs = kosmos.authenticationInteractor.lockoutEndTimestamp ?: 0 advanceTimeBy(lockoutEndMs - testScope.currentTime) assertThat(isInputEnabled).isTrue() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt index 312c14df9505..fec56ed919de 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepositoryImplTest.kt @@ -18,9 +18,13 @@ package com.android.systemui.brightness.data.repository import android.content.applicationContext import android.os.UserManager -import androidx.test.ext.junit.runners.AndroidJUnit4 +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization +import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf import androidx.test.filters.SmallTest import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.systemui.Flags.FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher @@ -40,14 +44,18 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class BrightnessPolicyRepositoryImplTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class BrightnessPolicyRepositoryImplTest(flags: FlagsParameterization) : SysuiTestCase() { - private val kosmos = testKosmos() + init { + mSetFlagsRule.setFlagsParameterization(flags) + } - private val fakeUserRepository = kosmos.fakeUserRepository + private val kosmos = testKosmos() private val mockUserRestrictionChecker: UserRestrictionChecker = mock { whenever(checkIfRestrictionEnforced(any(), anyString(), anyInt())).thenReturn(null) @@ -130,7 +138,83 @@ class BrightnessPolicyRepositoryImplTest : SysuiTestCase() { } } - private companion object { - val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS + @Test + @DisableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION) + fun brightnessBaseUserRestriction_flagOff_noRestriction() = + with(kosmos) { + testScope.runTest { + whenever( + mockUserRestrictionChecker.hasBaseUserRestriction( + any(), + eq(RESTRICTION), + eq(userRepository.getSelectedUserInfo().id) + ) + ) + .thenReturn(true) + + val restrictions by collectLastValue(underTest.restrictionPolicy) + + assertThat(restrictions).isEqualTo(PolicyRestriction.NoRestriction) + } + } + + @Test + fun bothRestrictions_returnsSetEnforcedAdminFromCheck() = + with(kosmos) { + testScope.runTest { + val enforcedAdmin: EnforcedAdmin = + EnforcedAdmin.createDefaultEnforcedAdminWithRestriction(RESTRICTION) + + whenever( + mockUserRestrictionChecker.checkIfRestrictionEnforced( + any(), + eq(RESTRICTION), + eq(userRepository.getSelectedUserInfo().id) + ) + ) + .thenReturn(enforcedAdmin) + + whenever( + mockUserRestrictionChecker.hasBaseUserRestriction( + any(), + eq(RESTRICTION), + eq(userRepository.getSelectedUserInfo().id) + ) + ) + .thenReturn(true) + + val restrictions by collectLastValue(underTest.restrictionPolicy) + + assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(enforcedAdmin)) + } + } + + @Test + @EnableFlags(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION) + fun brightnessBaseUserRestriction_flagOn_emptyRestriction() = + with(kosmos) { + testScope.runTest { + whenever( + mockUserRestrictionChecker.hasBaseUserRestriction( + any(), + eq(RESTRICTION), + eq(userRepository.getSelectedUserInfo().id) + ) + ) + .thenReturn(true) + + val restrictions by collectLastValue(underTest.restrictionPolicy) + + assertThat(restrictions).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin())) + } + } + + companion object { + private const val RESTRICTION = UserManager.DISALLOW_CONFIG_BRIGHTNESS + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return allCombinationsOf(FLAG_ENFORCE_BRIGHTNESS_BASE_USER_RESTRICTION) + } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt index 85a4bcf62223..11f523846268 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/BrightnessPolicyEnforcementInteractorTest.kt @@ -48,7 +48,6 @@ class BrightnessPolicyEnforcementInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val mockActivityStarter = kosmos.activityStarter - private val fakeBrightnessPolicyEnforcementInteractor = kosmos.fakeBrightnessPolicyRepository private val underTest = with(kosmos) { @@ -70,7 +69,18 @@ class BrightnessPolicyEnforcementInteractorTest : SysuiTestCase() { fakeBrightnessPolicyRepository.setCurrentUserRestricted() - assertThat(restriction).isInstanceOf(PolicyRestriction.Restricted::class.java) + assertThat(restriction) + .isEqualTo( + PolicyRestriction.Restricted( + EnforcedAdmin.createDefaultEnforcedAdminWithRestriction( + BrightnessPolicyRepository.RESTRICTION + ) + ) + ) + + fakeBrightnessPolicyRepository.setBaseUserRestriction() + + assertThat(restriction).isEqualTo(PolicyRestriction.Restricted(EnforcedAdmin())) } } 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/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index b4b812d60a1a..0ab09595d6b0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -265,7 +265,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is dreaming and on communal. - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -282,7 +282,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is not dreaming and on communal. - fakeKeyguardRepository.setDreaming(false) + updateDreaming(false) communalInteractor.changeScene(CommunalScenes.Communal) // Scene stays as Communal @@ -297,7 +297,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is dreaming and on communal. - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -309,7 +309,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Dream stops, timeout is cancelled and device stays on hub, because the regular // screen timeout will take effect at this point. - fakeKeyguardRepository.setDreaming(false) + updateDreaming(false) advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) assertThat(scene).isEqualTo(CommunalScenes.Communal) } @@ -320,7 +320,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { with(kosmos) { testScope.runTest { // Device is on communal, but not dreaming. - fakeKeyguardRepository.setDreaming(false) + updateDreaming(false) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -328,7 +328,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { // Wait a bit, but not long enough to timeout, then start dreaming. advanceTimeBy((SCREEN_TIMEOUT / 2).milliseconds) - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) assertThat(scene).isEqualTo(CommunalScenes.Communal) // Device times out after one screen timeout interval, dream doesn't reset timeout. @@ -338,11 +338,31 @@ class CommunalSceneStartableTest : SysuiTestCase() { } @Test + fun hubTimeout_dreamAfterInitialTimeout_goesToBlank() = + with(kosmos) { + testScope.runTest { + // Device is on communal. + communalInteractor.changeScene(CommunalScenes.Communal) + + // Device stays on the hub after the timeout since we're not dreaming. + advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2) + val scene by collectLastValue(communalInteractor.desiredScene) + assertThat(scene).isEqualTo(CommunalScenes.Communal) + + // Start dreaming. + updateDreaming(true) + + // Hub times out immediately. + assertThat(scene).isEqualTo(CommunalScenes.Blank) + } + } + + @Test fun hubTimeout_userActivityTriggered_resetsTimeout() = with(kosmos) { testScope.runTest { // Device is dreaming and on communal. - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -371,7 +391,7 @@ class CommunalSceneStartableTest : SysuiTestCase() { fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT * 2) // Device is dreaming and on communal. - fakeKeyguardRepository.setDreaming(true) + updateDreaming(true) communalInteractor.changeScene(CommunalScenes.Communal) val scene by collectLastValue(communalInteractor.desiredScene) @@ -395,6 +415,12 @@ class CommunalSceneStartableTest : SysuiTestCase() { runCurrent() } + private fun TestScope.updateDreaming(dreaming: Boolean) = + with(kosmos) { + fakeKeyguardRepository.setDreaming(dreaming) + runCurrent() + } + private suspend fun TestScope.enableCommunal() = with(kosmos) { setCommunalAvailable(true) 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/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index ce7b60e86f8f..325a324da8ad 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -29,6 +29,7 @@ import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.settingslib.flags.Flags.FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher @@ -202,6 +203,18 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD) } + @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT) + @Test + fun hubShowsAllWidgetsByDefaultWhenFlagEnabled() = + testScope.runTest { + val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER)) + assertThat(setting?.categories) + .isEqualTo( + AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD + + AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN + ) + } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) 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 497180b60151..83227e1fc597 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 @@ -57,6 +58,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.activityStarter @@ -201,14 +204,14 @@ class CommunalInteractorTest : SysuiTestCase() { } @Test - fun isCommunalAvailable_whenDreaming_true() = + fun isCommunalAvailable_whenKeyguardShowing_true() = testScope.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() keyguardRepository.setIsEncryptedOrLockdown(false) userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setDreaming(true) + keyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isTrue() } @@ -586,6 +589,7 @@ class CommunalInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, + currentScene = flowOf(targetScene), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -630,6 +634,7 @@ class CommunalInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, + currentScene = flowOf(targetScene), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -748,6 +753,7 @@ class CommunalInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = CommunalScenes.Communal, toScene = CommunalScenes.Blank, + currentScene = flowOf(CommunalScenes.Blank), progress = flowOf(0f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -776,6 +782,7 @@ class CommunalInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = CommunalScenes.Blank, toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Communal), progress = flowOf(0f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -795,6 +802,7 @@ class CommunalInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = CommunalScenes.Communal, toScene = CommunalScenes.Blank, + currentScene = flowOf(CommunalScenes.Blank), progress = flowOf(1.0f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -864,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) } } @@ -1030,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) @@ -1042,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 @@ -1069,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) @@ -1079,10 +1092,83 @@ 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 + fun showCommunalFromOccluded_enteredOccludedFromHub() = + testScope.runTest { + kosmos.setCommunalAvailable(true) + val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) + assertThat(showCommunalFromOccluded).isFalse() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + testScope + ) + + assertThat(showCommunalFromOccluded).isTrue() + } + + @Test + fun showCommunalFromOccluded_enteredOccludedFromLockscreen() = + testScope.runTest { + kosmos.setCommunalAvailable(true) + val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) + assertThat(showCommunalFromOccluded).isFalse() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + testScope + ) + + assertThat(showCommunalFromOccluded).isFalse() + } + + @Test + fun showCommunalFromOccluded_communalBecomesUnavailableWhileOccluded() = + testScope.runTest { + kosmos.setCommunalAvailable(true) + val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) + assertThat(showCommunalFromOccluded).isFalse() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + testScope + ) + runCurrent() + kosmos.setCommunalAvailable(false) + + assertThat(showCommunalFromOccluded).isFalse() + } + + @Test + fun showCommunalFromOccluded_showBouncerWhileOccluded() = + testScope.runTest { + kosmos.setCommunalAvailable(true) + val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) + assertThat(showCommunalFromOccluded).isFalse() + + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + testScope + ) + runCurrent() + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.OCCLUDED, + to = KeyguardState.PRIMARY_BOUNCER, + testScope + ) + + assertThat(showCommunalFromOccluded).isTrue() } private fun smartspaceTimer(id: String, timestamp: Long = 0L): SmartspaceTarget { @@ -1103,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 { @@ -1114,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/log/CommunalLoggerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt index a51315bd96b8..def8698b7e4b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/log/CommunalLoggerStartableTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.testKosmos import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -194,6 +195,7 @@ class CommunalLoggerStartableTest : SysuiTestCase() { return ObservableTransitionState.Transition( fromScene = from, toScene = to, + currentScene = flowOf(to), progress = emptyFlow(), isInitiatedByUserInput = true, isUserInputOngoing = emptyFlow(), 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..be44339bab8d 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 @@ -52,6 +51,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState @@ -90,7 +90,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 +111,7 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { private lateinit var underTest: CommunalViewModel init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -142,10 +142,10 @@ class CommunalViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() { testScope, context.resources, kosmos.keyguardTransitionInteractor, + kosmos.keyguardInteractor, kosmos.communalInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, - kosmos.deviceEntryInteractor, mediaHost, logcatLogBuffer("CommunalViewModelTest"), ) @@ -186,12 +186,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 +245,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/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 20beabb983da..2546f27cb351 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.domain.interactor.displayStateInteractor import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.coroutines.FlowValue import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues @@ -144,6 +145,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository private val testScope = kosmos.testScope private val fakeUserRepository = kosmos.fakeUserRepository + private val fakeExecutor = kosmos.fakeExecutor private lateinit var authStatus: FlowValue<FaceAuthenticationStatus?> private lateinit var detectStatus: FlowValue<FaceDetectionStatus?> private lateinit var authRunning: FlowValue<Boolean?> @@ -220,12 +222,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { testScope.backgroundScope, testDispatcher, testDispatcher, + fakeExecutor, sessionTracker, uiEventLogger, FaceAuthenticationLogger(logcatLogBuffer("DeviceEntryFaceAuthRepositoryLog")), biometricSettingsRepository, deviceEntryFingerprintAuthRepository, - trustRepository, keyguardRepository, powerInteractor, keyguardInteractor, @@ -292,6 +294,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { fun faceLockoutStatusIsPropagated() = testScope.runTest { initCollectors() + fakeExecutor.runAllReady() verify(faceManager).addLockoutResetCallback(faceLockoutResetCallback.capture()) allPreconditionsToRunFaceAuthAreTrue() @@ -1177,6 +1180,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } private suspend fun TestScope.allPreconditionsToRunFaceAuthAreTrue() { + fakeExecutor.runAllReady() verify(faceManager, atLeastOnce()) .addLockoutResetCallback(faceLockoutResetCallback.capture()) trustRepository.setCurrentUserTrusted(false) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt index 88ad3f37dacd..3005d0b0d160 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorTest.kt @@ -22,7 +22,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos @@ -70,8 +70,10 @@ class AuthRippleInteractorTest : SysuiTestCase() { fun faceUnlocked_showsAuthRipple() = testScope.runTest { val showUnlockRipple by collectLastValue(underTest.showUnlockRipple) - keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR) - keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + keyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockSource.FACE_SENSOR + ) assertThat(showUnlockRipple).isEqualTo(BiometricUnlockSource.FACE_SENSOR) } @@ -79,8 +81,10 @@ class AuthRippleInteractorTest : SysuiTestCase() { fun fingerprintUnlocked_showsAuthRipple() = testScope.runTest { val showUnlockRippleFromBiometricUnlock by collectLastValue(underTest.showUnlockRipple) - keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR) - keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + keyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockSource.FINGERPRINT_SENSOR + ) assertThat(showUnlockRippleFromBiometricUnlock) .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt index d216fa0d0eff..2e4c97bc80d0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractorTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos @@ -46,8 +46,10 @@ class DeviceEntrySourceInteractorTest : SysuiTestCase() { testScope.runTest { val deviceEntryFromBiometricAuthentication by collectLastValue(underTest.deviceEntryFromBiometricSource) - keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR) - keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + keyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockSource.FACE_SENSOR, + ) runCurrent() assertThat(deviceEntryFromBiometricAuthentication) .isEqualTo(BiometricUnlockSource.FACE_SENSOR) @@ -57,8 +59,10 @@ class DeviceEntrySourceInteractorTest : SysuiTestCase() { fun deviceEntryFromFingerprintUnlock() = runTest { val deviceEntryFromBiometricAuthentication by collectLastValue(underTest.deviceEntryFromBiometricSource) - keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR) - keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + keyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockSource.FINGERPRINT_SENSOR, + ) runCurrent() assertThat(deviceEntryFromBiometricAuthentication) .isEqualTo(BiometricUnlockSource.FINGERPRINT_SENSOR) @@ -68,9 +72,10 @@ class DeviceEntrySourceInteractorTest : SysuiTestCase() { fun noDeviceEntry() = runTest { val deviceEntryFromBiometricAuthentication by collectLastValue(underTest.deviceEntryFromBiometricSource) - keyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR) - // doesn't dismiss keyguard: - keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.ONLY_WAKE) + keyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.ONLY_WAKE, // doesn't dismiss keyguard: + BiometricUnlockSource.FINGERPRINT_SENSOR, + ) runCurrent() assertThat(deviceEntryFromBiometricAuthentication).isNull() } 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..f5c86e092a26 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java @@ -27,11 +27,15 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.DreamManager; import android.content.res.Resources; import android.graphics.Region; import android.os.Handler; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.TestableLooper.RunWithLooper; import android.view.AttachedSurfaceControl; +import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.ViewTreeObserver; @@ -41,12 +45,15 @@ import androidx.test.filters.SmallTest; import com.android.dream.lowlight.LowLightTransitionCoordinator; import com.android.keyguard.BouncerPanelExpansionCalculator; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; +import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.complication.ComplicationHostViewController; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.BlurUtils; import kotlinx.coroutines.CoroutineDispatcher; @@ -91,6 +98,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { ViewGroup mDreamOverlayContentView; @Mock + View mHubGestureIndicatorView; + + @Mock Handler mHandler; @Mock @@ -115,6 +125,12 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { DreamOverlayStateController mStateController; @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor; + @Mock + ShadeInteractor mShadeInteractor; + @Mock + CommunalInteractor mCommunalInteractor; + @Mock + private DreamManager mDreamManager; DreamOverlayContainerViewController mController; @@ -133,6 +149,7 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mDreamOverlayContainerView, mComplicationHostViewController, mDreamOverlayContentView, + mHubGestureIndicatorView, mDreamOverlayStatusBarViewController, mLowLightTransitionCoordinator, mBlurUtils, @@ -146,7 +163,22 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase { mAnimationsController, mStateController, mBouncerlessScrimController, - mKeyguardTransitionInteractor); + mKeyguardTransitionInteractor, + mShadeInteractor, + mCommunalInteractor, + mDreamManager); + } + + @DisableFlags(Flags.FLAG_COMMUNAL_HUB) + @Test + public void testHubGestureIndicatorGoneWhenFlagOff() { + verify(mHubGestureIndicatorView, never()).setVisibility(View.VISIBLE); + } + + @EnableFlags({Flags.FLAG_COMMUNAL_HUB, Flags.FLAG_GLANCEABLE_HUB_GESTURE_HANDLE}) + @Test + public void testHubGestureIndicatorVisibleWhenFlagOn() { + verify(mHubGestureIndicatorView).setVisibility(View.VISIBLE); } @Test @@ -170,7 +202,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 +313,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/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java index e3c6deed1527..29fbee01a18b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java @@ -108,7 +108,7 @@ public class CommunalTouchHandlerTest extends SysuiTestCase { mTouchHandler.onSessionStart(mTouchSession); verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture()); inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent); - verify(mCentralSurfaces).handleExternalShadeWindowTouch(motionEvent); + verify(mCentralSurfaces).handleDreamTouch(motionEvent); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index 19b80da62dc7..f375ec7b8884 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -30,6 +30,7 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.dreams.DreamOverlayCallbackController +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel @@ -515,11 +516,9 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { fun biometricUnlockSource() = testScope.runTest { val values = mutableListOf<BiometricUnlockSource?>() - val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this) + val job = underTest.biometricUnlockState.onEach { values.add(it.source) }.launchIn(this) runCurrent() - val captor = argumentCaptor<KeyguardUpdateMonitorCallback>() - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) // An initial, null value should be initially emitted so that flows combined with this // one @@ -535,7 +534,10 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { BiometricSourceType.FINGERPRINT, ) .onEach { biometricSourceType -> - captor.value.onBiometricAuthenticated(0, biometricSourceType, false) + underTest.setBiometricUnlockState( + BiometricUnlockMode.NONE, + BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType) + ) runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt index 69a1a0f3196e..0bfcd548881b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt @@ -24,7 +24,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.kosmos.testScope import com.android.systemui.power.data.repository.powerRepository @@ -89,7 +89,10 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { // The source and sensor locations are still null, so we should still be using the // default reveal despite a biometric unlock. - fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockSource.FINGERPRINT_SENSOR + ) runCurrent() values.assertEffectsMatchPredicates( @@ -98,7 +101,10 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { // We got a source but still have no sensor locations, so should be sticking with // the default effect. - fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR) + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockSource.FINGERPRINT_SENSOR + ) runCurrent() values.assertEffectsMatchPredicates( @@ -117,8 +123,10 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { // Now we have fingerprint sensor locations, and wake and unlock via fingerprint. val fingerprintLocation = Point(500, 500) fakeKeyguardRepository.setFingerprintSensorLocation(fingerprintLocation) - fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FINGERPRINT_SENSOR) - fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING) + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING, + BiometricUnlockSource.FINGERPRINT_SENSOR + ) // We should now have switched to the circle reveal, at the fingerprint location. runCurrent() @@ -133,14 +141,21 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { // Subsequent wake and unlocks should not emit duplicate, identical CircleReveals. val valuesPrevSize = values.size - fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING) fakeKeyguardRepository.setBiometricUnlockState( - BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING, + BiometricUnlockSource.FINGERPRINT_SENSOR + ) + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM, + BiometricUnlockSource.FINGERPRINT_SENSOR ) assertEquals(valuesPrevSize, values.size) // Non-biometric unlock, we should return to the default reveal. - fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE) + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.NONE, + BiometricUnlockSource.FINGERPRINT_SENSOR + ) runCurrent() values.assertEffectsMatchPredicates( @@ -155,9 +170,10 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { // We already have a face location, so switching to face source should update the // CircleReveal. - fakeKeyguardRepository.setBiometricUnlockSource(BiometricUnlockSource.FACE_SENSOR) - runCurrent() - fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockSource.FACE_SENSOR + ) runCurrent() values.assertEffectsMatchPredicates( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt index 360f284f3072..d630a2f64c5f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt @@ -43,8 +43,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository -import com.android.systemui.keyguard.data.repository.keyguardRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +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.keyguard.shared.model.TransitionStep @@ -99,7 +98,7 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { to = KeyguardState.AOD, testScope ) - kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE) + kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE) reset(transitionRepository) } } @@ -278,7 +277,9 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR) fun testWakeAndUnlock_transitionsToGone_onlyAfterDismissCallPostWakeup() = testScope.runTest { - kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + kosmos.fakeKeyguardRepository.setBiometricUnlockState( + BiometricUnlockMode.WAKE_AND_UNLOCK + ) powerInteractor.setAwakeForTest() runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 12f891835d0e..addbdb664c77 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -24,23 +24,23 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository -import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer -import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.fakeCommandQueue import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope -import com.android.systemui.power.domain.interactor.PowerInteractorFactory +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.shade.data.repository.FakeShadeRepository -import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor +import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -63,30 +63,16 @@ class KeyguardInteractorTest : SysuiTestCase() { private val repository by lazy { kosmos.fakeKeyguardRepository } private val sceneInteractor by lazy { kosmos.sceneInteractor } private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor } - private val commandQueue by lazy { FakeCommandQueue() } - private val bouncerRepository = FakeKeyguardBouncerRepository() - private val shadeRepository = FakeShadeRepository() - private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val commandQueue by lazy { kosmos.fakeCommandQueue } + private val configRepository by lazy { kosmos.fakeConfigurationRepository } + private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository } + private val shadeRepository by lazy { kosmos.shadeRepository } + private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } + private val transitionState: MutableStateFlow<ObservableTransitionState> = MutableStateFlow(ObservableTransitionState.Idle(Scenes.Gone)) - private val underTest by lazy { - KeyguardInteractor( - repository = repository, - commandQueue = commandQueue, - powerInteractor = PowerInteractorFactory.create().powerInteractor, - bouncerRepository = bouncerRepository, - configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()), - shadeRepository = shadeRepository, - keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, - sceneInteractorProvider = { sceneInteractor }, - fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, - sharedNotificationContainerInteractor = { - kosmos.sharedNotificationContainerInteractor - }, - applicationScope = testScope, - ) - } + private val underTest by lazy { kosmos.keyguardInteractor } @Before fun setUp() { @@ -247,6 +233,106 @@ class KeyguardInteractorTest : SysuiTestCase() { } @Test + fun keyguardTranslationY_whenGoneEmitsZero() = + testScope.runTest { + val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY) + + configRepository.setDimensionPixelSize( + R.dimen.keyguard_translate_distance_on_swipe_up, + 100 + ) + configRepository.onAnyConfigurationChange() + + shadeRepository.setLegacyShadeExpansion(0f) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + testScope, + ) + + assertThat(keyguardTranslationY).isEqualTo(0f) + } + + @Test + fun keyguardTranslationY_whenNotGoneAndShadeIsFullyCollapsedEmitsZero() = + testScope.runTest { + val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY) + + configRepository.setDimensionPixelSize( + R.dimen.keyguard_translate_distance_on_swipe_up, + 100 + ) + configRepository.onAnyConfigurationChange() + + shadeRepository.setLegacyShadeExpansion(0f) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + assertThat(keyguardTranslationY).isEqualTo(0f) + } + + @Test + fun keyguardTranslationY_whenNotGoneAndShadeIsReesetEmitsZero() = + testScope.runTest { + val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY) + + configRepository.setDimensionPixelSize( + R.dimen.keyguard_translate_distance_on_swipe_up, + 100 + ) + configRepository.onAnyConfigurationChange() + + shadeRepository.setLegacyShadeExpansion(1f) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + testScope, + ) + + assertThat(keyguardTranslationY).isEqualTo(0f) + } + + @Test + fun keyguardTranslationY_whenTransitioningToGoneAndShadeIsExpandingEmitsNonZero() = + testScope.runTest { + val keyguardTranslationY by collectLastValue(underTest.keyguardTranslationY) + + configRepository.setDimensionPixelSize( + R.dimen.keyguard_translate_distance_on_swipe_up, + 100 + ) + configRepository.onAnyConfigurationChange() + + shadeRepository.setLegacyShadeExpansion(0.5f) + + keyguardTransitionRepository.sendTransitionSteps( + listOf( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + value = 0f, + transitionState = TransitionState.STARTED, + ), + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.GONE, + value = 0.1f, + transitionState = TransitionState.RUNNING, + ), + ), + testScope, + ) + + assertThat(keyguardTranslationY).isGreaterThan(0f) + } + + @Test @EnableSceneContainer fun animationDozingTransitions() = testScope.runTest { @@ -265,6 +351,7 @@ class KeyguardInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Gone, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index 412292554e73..99cccb282264 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -19,9 +19,13 @@ 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.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.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING @@ -29,36 +33,74 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE 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.KeyguardState.UNDEFINED import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED 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 junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertThrows +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) @kotlinx.coroutines.ExperimentalCoroutinesApi -@android.platform.test.annotations.EnabledOnRavenwood class KeyguardTransitionInteractorTest : SysuiTestCase() { val kosmos = testKosmos() val underTest = kosmos.keyguardTransitionInteractor val repository = kosmos.fakeKeyguardTransitionRepository val testScope = kosmos.testScope + private val sceneTransitions = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + + private val lsToGone = + ObservableTransitionState.Transition( + Scenes.Lockscreen, + Scenes.Gone, + flowOf(Scenes.Lockscreen), + flowOf(0f), + false, + flowOf(false) + ) + + private val goneToLs = + ObservableTransitionState.Transition( + Scenes.Gone, + Scenes.Lockscreen, + flowOf(Scenes.Lockscreen), + flowOf(0f), + false, + flowOf(false) + ) + + @Before + fun setUp() { + kosmos.sceneContainerRepository.setTransitionState(sceneTransitions) + } + @Test fun transitionCollectorsReceivesOnlyAppropriateEvents() = testScope.runTest { - val lockscreenToAodSteps by collectValues(underTest.transition(LOCKSCREEN, AOD)) - val aodToLockscreenSteps by collectValues(underTest.transition(AOD, LOCKSCREEN)) + val lockscreenToAodSteps by + collectValues(underTest.transition(Edge.create(LOCKSCREEN, AOD))) + val aodToLockscreenSteps by + collectValues(underTest.transition(Edge.create(AOD, LOCKSCREEN))) val steps = mutableListOf<TransitionStep>() steps.add(TransitionStep(AOD, GONE, 0f, STARTED)) @@ -235,7 +277,13 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { .isEqualTo( listOf( // The initial transition will also get sent when collect started - TransitionStep(OFF, LOCKSCREEN, 0f, STARTED), + TransitionStep( + OFF, + LOCKSCREEN, + 0f, + STARTED, + ownerName = "KeyguardTransitionRepository(boot)" + ), steps[0], steps[3], steps[6] @@ -476,6 +524,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun isInTransitionToState() = testScope.runTest { val results by collectValues(underTest.isInTransitionToState(GONE)) @@ -580,7 +629,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, STARTED), + TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED), ) assertThat(results) @@ -592,7 +641,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, RUNNING), + TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING), ) assertThat(results) @@ -604,7 +653,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, FINISHED), + TransitionStep(DOZING, LOCKSCREEN, 0f, FINISHED), ) assertThat(results) @@ -617,9 +666,9 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(GONE, DOZING, 0f, STARTED), - TransitionStep(GONE, DOZING, 0f, RUNNING), - TransitionStep(GONE, DOZING, 1f, FINISHED), + TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED), + TransitionStep(LOCKSCREEN, DOZING, 0f, RUNNING), + TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED), ) assertThat(results) @@ -632,8 +681,8 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) sendSteps( - TransitionStep(DOZING, GONE, 0f, STARTED), - TransitionStep(DOZING, GONE, 0f, RUNNING), + TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED), + TransitionStep(DOZING, LOCKSCREEN, 0f, RUNNING), ) assertThat(results) @@ -1398,6 +1447,143 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { ) } + @Test + @DisableSceneContainer + fun transition_no_conversion_with_flag_off() = + testScope.runTest { + val currentStates by + collectValues(underTest.transition(Edge.create(PRIMARY_BOUNCER, GONE))) + + val sendStep1 = TransitionStep(PRIMARY_BOUNCER, GONE, 0f, STARTED) + sendSteps(sendStep1) + + assertEquals(listOf(sendStep1), currentStates) + } + + @Test + @EnableSceneContainer + fun transition_conversion_with_flag_on() = + testScope.runTest { + val currentStates by + collectValues(underTest.transition(Edge.create(PRIMARY_BOUNCER, GONE))) + + val sendStep1 = TransitionStep(PRIMARY_BOUNCER, GONE, 0f, STARTED) + sendSteps(sendStep1) + + assertEquals(listOf<TransitionStep>(), currentStates) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_values_with_sceneContainer_in_correct_state() = + testScope.runTest { + val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE))) + val currentStatesConverted by + collectValues(underTest.transition(Edge.create(LOCKSCREEN, UNDEFINED))) + + sceneTransitions.value = lsToGone + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3) + + assertEquals(listOf(sendStep1, sendStep2), currentStates) + assertEquals(listOf(sendStep1, sendStep2), currentStatesConverted) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_nothing_with_sceneContainer_in_wrong_state() = + testScope.runTest { + val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, GONE))) + + sceneTransitions.value = goneToLs + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3) + + assertEquals(listOf<TransitionStep>(), currentStates) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_values_when_edge_within_lockscreen_scene() = + testScope.runTest { + val currentStates by + collectValues(underTest.transition(Edge.create(LOCKSCREEN, DOZING))) + + sceneTransitions.value = goneToLs + val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED) + val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3) + + assertEquals(listOf(sendStep1, sendStep2), currentStates) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_values_with_null_edge_within_lockscreen_scene() = + testScope.runTest { + val currentStates by collectValues(underTest.transition(Edge.create(LOCKSCREEN, null))) + val currentStatesReversed by + collectValues(underTest.transition(Edge.create(null, LOCKSCREEN))) + + sceneTransitions.value = goneToLs + val sendStep1 = TransitionStep(LOCKSCREEN, DOZING, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, DOZING, 1f, FINISHED) + val sendStep3 = TransitionStep(LOCKSCREEN, AOD, 0f, STARTED) + val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3, sendStep4) + + assertEquals(listOf(sendStep1, sendStep2, sendStep3), currentStates) + assertEquals(listOf(sendStep4), currentStatesReversed) + } + + @Test + @EnableSceneContainer + fun transition_conversion_emits_values_with_null_edge_out_of_lockscreen_scene() = + testScope.runTest { + val currentStates by collectValues(underTest.transition(Edge.create(null, UNDEFINED))) + val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE))) + + sceneTransitions.value = lsToGone + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED) + val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3, sendStep4) + + assertEquals(listOf(sendStep1, sendStep2), currentStates) + assertEquals(listOf(sendStep1, sendStep2), currentStatesMapped) + } + + @Test + @EnableSceneContainer + fun transition_conversion_does_not_emit_with_null_edge_with_wrong_stl_state() = + testScope.runTest { + val currentStatesMapped by collectValues(underTest.transition(Edge.create(null, GONE))) + + sceneTransitions.value = goneToLs + val sendStep1 = TransitionStep(LOCKSCREEN, UNDEFINED, 0f, STARTED) + val sendStep2 = TransitionStep(LOCKSCREEN, UNDEFINED, 1f, FINISHED) + val sendStep3 = TransitionStep(UNDEFINED, AOD, 0f, STARTED) + val sendStep4 = TransitionStep(AOD, LOCKSCREEN, 0f, STARTED) + sendSteps(sendStep1, sendStep2, sendStep3, sendStep4) + + assertEquals(listOf<TransitionStep>(), currentStatesMapped) + } + + @Test + @EnableSceneContainer + fun transition_null_edges_throw() = + testScope.runTest { + assertThrows(IllegalStateException::class.java) { + underTest.transition(Edge.create(null, null)) + } + } + private suspend fun sendSteps(vararg steps: TransitionStep) { steps.forEach { repository.sendTransitionStep(it) 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/KeyguardTransitionAnimationFlowTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt index 0ac7ff5232a3..a0fed6bbfc2c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlowTest.kt @@ -23,11 +23,13 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE 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.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.time.Duration.Companion.milliseconds @@ -50,11 +52,14 @@ class KeyguardTransitionAnimationFlowTest : SysuiTestCase() { @Before fun setUp() { underTest = - animationFlow.setup( - duration = 1000.milliseconds, - from = GONE, - to = DREAMING, - ) + animationFlow + .setup( + duration = 1000.milliseconds, + edge = Edge.create(from = Scenes.Gone, to = DREAMING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = DREAMING), + ) } @Test(expected = IllegalArgumentException::class) 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 31b67b43adc4..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 @@ -16,36 +16,57 @@ 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.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.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 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 platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest -@RunWith(AndroidJUnit4::class) -class AodToLockscreenTransitionViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class AodToLockscreenTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { val kosmos = testKosmos() val testScope = kosmos.testScope val repository = kosmos.fakeKeyguardTransitionRepository - val shadeRepository = kosmos.fakeShadeRepository + val shadeTestUtil by lazy { kosmos.shadeTestUtil } val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository - val underTest = kosmos.aodToLockscreenTransitionViewModel + lateinit var underTest: AodToLockscreenTransitionViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setup() { + underTest = kosmos.aodToLockscreenTransitionViewModel + } @Test fun deviceEntryParentViewShows() = @@ -65,7 +86,7 @@ class AodToLockscreenTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val alpha by collectLastValue(underTest.notificationAlpha) - shadeRepository.setQsExpansion(0.5f) + shadeTestUtil.setQsExpansion(0.5f) runCurrent() repository.sendTransitionStep(step(0f, TransitionState.STARTED)) @@ -81,7 +102,7 @@ class AodToLockscreenTransitionViewModelTest : SysuiTestCase() { testScope.runTest { val alpha by collectLastValue(underTest.notificationAlpha) - shadeRepository.setQsExpansion(0f) + shadeTestUtil.setQsExpansion(0f) runCurrent() repository.sendTransitionStep(step(0f, TransitionState.STARTED)) 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..7a9bd924afa4 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,25 +59,41 @@ 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 + @BrokenWithSceneContainer(339465026) fun scrimAlpha_runDimissFromKeyguard_shadeExpanded() = testScope.runTest { 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 +113,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) @@ -123,6 +138,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun scrimBehindAlpha_leaveShadeOpen() = testScope.runTest { val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) @@ -147,6 +163,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun showAllNotifications_isTrue_whenLeaveShadeOpen() = testScope.runTest { val showAllNotifications by @@ -163,6 +180,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun showAllNotifications_isFalse_whenLeaveShadeIsNotOpen() = testScope.runTest { val showAllNotifications by @@ -179,6 +197,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(330311871) fun scrimBehindAlpha_doNotLeaveShadeOpen() = testScope.runTest { val values by collectValues(underTest.scrimAlpha(500.milliseconds, PRIMARY_BOUNCER)) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt new file mode 100644 index 000000000000..68fbd1c44ad7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository +import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class DeviceEntryIconViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository + private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository + private lateinit var deviceEntryIconTransition: FakeDeviceEntryIconTransition + private lateinit var underTest: DeviceEntryIconViewModel + + @Before + fun setUp() { + keyguardRepository = kosmos.fakeKeyguardRepository + fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + fingerprintAuthRepository = kosmos.fakeDeviceEntryFingerprintAuthRepository + deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition + underTest = kosmos.deviceEntryIconViewModel + } + + @Test + fun isLongPressEnabled_udfpsRunning() = + testScope.runTest { + val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled) + setUpState( + isUdfpsSupported = true, + isUdfpsRunning = true, + ) + assertThat(isLongPressEnabled).isFalse() + } + + @Test + fun isLongPressEnabled_unlocked() = + testScope.runTest { + val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled) + setUpState( + isUdfpsSupported = true, + isLockscreenDismissible = true, + ) + assertThat(isLongPressEnabled).isTrue() + } + + @Test + fun isLongPressEnabled_lock() = + testScope.runTest { + val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled) + setUpState(isUdfpsSupported = true) + + // udfps supported + assertThat(isLongPressEnabled).isTrue() + + // udfps isn't supported + fingerprintPropertyRepository.supportsRearFps() + assertThat(isLongPressEnabled).isFalse() + } + + @Test + fun isVisible() = + testScope.runTest { + val isVisible by collectLastValue(underTest.isVisible) + deviceEntryIconTransitionAlpha(1f) + assertThat(isVisible).isTrue() + + deviceEntryIconTransitionAlpha(0f) + assertThat(isVisible).isFalse() + + deviceEntryIconTransitionAlpha(.5f) + assertThat(isVisible).isTrue() + } + + @Test + @DisableSceneContainer + fun iconType_fingerprint() = + testScope.runTest { + val iconType by collectLastValue(underTest.iconType) + setUpState( + isUdfpsSupported = true, + isUdfpsRunning = true, + ) + assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.FINGERPRINT) + } + + @Test + @DisableSceneContainer + fun iconType_locked() = + testScope.runTest { + val iconType by collectLastValue(underTest.iconType) + setUpState() + assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.LOCK) + } + + @Test + @DisableSceneContainer + fun iconType_unlocked() = + testScope.runTest { + val iconType by collectLastValue(underTest.iconType) + setUpState(isLockscreenDismissible = true) + assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.UNLOCK) + } + + @Test + @DisableSceneContainer + fun iconType_none() = + testScope.runTest { + val iconType by collectLastValue(underTest.iconType) + setUpState( + isUdfpsSupported = true, + isUdfpsRunning = true, + isLockscreenDismissible = true, + ) + assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.NONE) + } + + @Test + @EnableSceneContainer + fun iconType_fingerprint_withSceneContainer() = + testScope.runTest { + val iconType by collectLastValue(underTest.iconType) + setUpState( + isUdfpsSupported = true, + isUdfpsRunning = true, + ) + assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.FINGERPRINT) + } + + @Test + @EnableSceneContainer + fun iconType_locked_withSceneContainer() = + testScope.runTest { + val iconType by collectLastValue(underTest.iconType) + setUpState() + assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.LOCK) + } + + @Test + @EnableSceneContainer + fun iconType_unlocked_withSceneContainer() = + testScope.runTest { + val iconType by collectLastValue(underTest.iconType) + setUpState( + isLockscreenDismissible = true, + ) + assertThat(iconType).isEqualTo(DeviceEntryIconView.IconType.UNLOCK) + } + + @Test + @EnableSceneContainer + fun iconType_none_withSceneContainer() = + testScope.runTest { + val iconType by collectLastValue(underTest.iconType) + setUpState( + isUdfpsSupported = true, + isUdfpsRunning = true, + isLockscreenDismissible = true, + ) + 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 + setUpState(isUdfpsSupported = true) + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE) + + // non-interactive lock icon + 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 + setUpState( + isUdfpsSupported = true, + isLockscreenDismissible = true, + ) + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.ENTER) + } + + private fun deviceEntryIconTransitionAlpha(alpha: Float) { + deviceEntryIconTransition.setDeviceEntryParentViewAlpha(alpha) + } + + private suspend fun TestScope.setUpState( + isUdfpsSupported: Boolean = false, + isUdfpsRunning: Boolean = false, + isLockscreenDismissible: Boolean = false, + ) { + if (isUdfpsSupported) { + fingerprintPropertyRepository.supportsUdfps() + } + if (isUdfpsRunning) { + check(isUdfpsSupported) { "Cannot set UDFPS as running if it's not supported!" } + fingerprintAuthRepository.setIsRunning(true) + } else { + fingerprintAuthRepository.setIsRunning(false) + } + if (isLockscreenDismissible) { + setLockscreenDismissible() + } else { + if (!SceneContainerFlag.isEnabled) { + keyguardRepository.setKeyguardDismissible(false) + } + } + runCurrent() + } + + private suspend fun TestScope.setLockscreenDismissible() { + if (SceneContainerFlag.isEnabled) { + // Need to set up a collection for the authentication to be propagated. + val unused by collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus) + runCurrent() + assertThat( + kosmos.authenticationInteractor.authenticate( + FakeAuthenticationRepository.DEFAULT_PIN + ) + ) + .isEqualTo(AuthenticationResult.SUCCEEDED) + } else { + keyguardRepository.setKeyguardDismissible(true) + } + advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay + } +} 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..20ffa3312fa6 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 @@ -30,6 +30,8 @@ import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.parameterizeSceneContainerFlag import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -37,7 +39,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor import com.android.systemui.statusbar.phone.dozeParameters @@ -49,6 +53,8 @@ import com.android.systemui.util.ui.stopAnimating import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest @@ -60,7 +66,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 +81,11 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() private val viewState = ViewStateAccessor() - // add to init block + private val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + companion object { @JvmStatic @Parameters(name = "{0}") @@ -85,7 +95,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -97,6 +107,7 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, ) } + kosmos.sceneContainerRepository.setTransitionState(transitionState) } @Test @@ -310,6 +321,32 @@ class KeyguardRootViewModelTest(flags: FlagsParameterization?) : SysuiTestCase() } @Test + @EnableSceneContainer + fun alpha_transitionToHub_isZero_scene_container() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha(viewState)) + + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Communal, + emptyFlow(), + emptyFlow(), + false, + emptyFlow() + ) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.UNDEFINED, + testScope, + ) + + assertThat(alpha).isEqualTo(0f) + } + + @Test + @DisableSceneContainer fun alpha_transitionToHub_isZero() = testScope.runTest { val alpha by collectLastValue(underTest.alpha(viewState)) 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 bef951554b50..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 @@ -16,13 +16,14 @@ 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.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues 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.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -32,31 +33,53 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope -import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.collect.Range 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 +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest -@RunWith(AndroidJUnit4::class) -class LockscreenToAodTransitionViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class LockscreenToAodTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope private val repository = kosmos.fakeKeyguardTransitionRepository - private val shadeRepository = kosmos.shadeRepository private val keyguardRepository = kosmos.fakeKeyguardRepository private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository private val biometricSettingsRepository = kosmos.biometricSettingsRepository - private val underTest = kosmos.lockscreenToAodTransitionViewModel + + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + lateinit var underTest: LockscreenToAodTransitionViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setup() { + underTest = kosmos.lockscreenToAodTransitionViewModel + } @Test fun backgroundViewAlpha_shadeNotExpanded() = @@ -195,11 +218,11 @@ class LockscreenToAodTransitionViewModelTest : SysuiTestCase() { private fun shadeExpanded(expanded: Boolean) { if (expanded) { - shadeRepository.setQsExpansion(1f) + shadeTestUtil.setQsExpansion(1f) } else { keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setQsExpansion(0f) - shadeRepository.setLockscreenShadeExpansion(0f) + shadeTestUtil.setQsExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) } } 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 8f04ec3814eb..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 @@ -18,24 +18,25 @@ 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.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues 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.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope -import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.ShadeTestUtil +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat @@ -45,10 +46,12 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class LockscreenToDreamingTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { @@ -56,14 +59,27 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { } private val testScope = kosmos.testScope private lateinit var repository: FakeKeyguardTransitionRepository - private lateinit var shadeRepository: ShadeRepository + private lateinit var shadeTestUtil: ShadeTestUtil private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var underTest: LockscreenToDreamingTransitionViewModel + // add to init block + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Before fun setUp() { repository = kosmos.fakeKeyguardTransitionRepository - shadeRepository = kosmos.shadeRepository + shadeTestUtil = kosmos.shadeTestUtil keyguardRepository = kosmos.fakeKeyguardRepository underTest = kosmos.lockscreenToDreamingTransitionViewModel } @@ -177,11 +193,11 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { private fun shadeExpanded(expanded: Boolean) { if (expanded) { - shadeRepository.setQsExpansion(1f) + shadeTestUtil.setQsExpansion(1f) } else { keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setQsExpansion(0f) - shadeRepository.setLockscreenShadeExpansion(0f) + shadeTestUtil.setQsExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) } } } 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 b120f8776d9d..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 @@ -18,27 +18,28 @@ 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.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues 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.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.ShadeTestUtil +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth.assertThat @@ -48,25 +49,40 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class LockscreenToOccludedTransitionViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope private lateinit var repository: FakeKeyguardTransitionRepository - private lateinit var shadeRepository: ShadeRepository + private lateinit var shadeTestUtil: ShadeTestUtil private lateinit var keyguardRepository: FakeKeyguardRepository private lateinit var configurationRepository: FakeConfigurationRepository private lateinit var underTest: LockscreenToOccludedTransitionViewModel + // add to init block + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + @Before fun setUp() { repository = kosmos.fakeKeyguardTransitionRepository - shadeRepository = kosmos.shadeRepository + shadeTestUtil = kosmos.shadeTestUtil keyguardRepository = kosmos.fakeKeyguardRepository configurationRepository = kosmos.fakeConfigurationRepository underTest = kosmos.lockscreenToOccludedTransitionViewModel @@ -200,11 +216,11 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { private fun shadeExpanded(expanded: Boolean) { if (expanded) { - shadeRepository.setQsExpansion(1f) + shadeTestUtil.setQsExpansion(1f) } else { keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setQsExpansion(0f) - shadeRepository.setLockscreenShadeExpansion(0f) + shadeTestUtil.setQsExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) } } } 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 43ab93a18118..1c1fcc450d73 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 @@ -16,11 +16,14 @@ 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.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +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.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -29,31 +32,63 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope -import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.testKosmos import com.google.common.collect.Range import com.google.common.truth.Truth import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi @SmallTest -@RunWith(AndroidJUnit4::class) -class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { +@RunWith(ParameterizedAndroidJunit4::class) +class LockscreenToPrimaryBouncerTransitionViewModelTest(flags: FlagsParameterization) : + SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope private val repository = kosmos.fakeKeyguardTransitionRepository - private val shadeRepository = kosmos.shadeRepository + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } private val keyguardRepository = kosmos.fakeKeyguardRepository - private val underTest = kosmos.lockscreenToPrimaryBouncerTransitionViewModel + private lateinit var underTest: LockscreenToPrimaryBouncerTransitionViewModel + + private val transitionState = + MutableStateFlow<ObservableTransitionState>( + ObservableTransitionState.Idle(Scenes.Lockscreen) + ) + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setup() { + underTest = kosmos.lockscreenToPrimaryBouncerTransitionViewModel + } @Test + @BrokenWithSceneContainer(330311871) fun deviceEntryParentViewAlpha_shadeExpanded() = testScope.runTest { val actual by collectLastValue(underTest.deviceEntryParentViewAlpha) @@ -85,6 +120,17 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { shadeExpanded(false) runCurrent() + kosmos.sceneContainerRepository.setTransitionState(transitionState) + transitionState.value = + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Bouncer, + emptyFlow(), + emptyFlow(), + false, + emptyFlow() + ) + runCurrent() // fade out repository.sendTransitionStep(step(0f, TransitionState.STARTED)) runCurrent() @@ -110,7 +156,9 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { ): TransitionStep { return TransitionStep( from = KeyguardState.LOCKSCREEN, - to = KeyguardState.PRIMARY_BOUNCER, + to = + if (SceneContainerFlag.isEnabled) KeyguardState.UNDEFINED + else KeyguardState.PRIMARY_BOUNCER, value = value, transitionState = state, ownerName = "LockscreenToPrimaryBouncerTransitionViewModelTest" @@ -119,11 +167,11 @@ class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { private fun shadeExpanded(expanded: Boolean) { if (expanded) { - shadeRepository.setQsExpansion(1f) + shadeTestUtil.setQsExpansion(1f) } else { keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) - shadeRepository.setQsExpansion(0f) - shadeRepository.setLockscreenShadeExpansion(0f) + shadeTestUtil.setQsExpansion(0f) + shadeTestUtil.setLockscreenShadeExpansion(0f) } } } 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..856c3fe19d73 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 @@ -41,16 +45,16 @@ import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.notificationLockscreenUserManager import com.android.systemui.statusbar.policy.keyguardStateController 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.google.common.truth.Truth.assertThat import kotlinx.coroutines.test.runTest 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.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @SmallTest @RunWith(AndroidJUnit4::class) @@ -117,7 +121,7 @@ class MediaControlInteractorTest : SysuiTestCase() { whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())) .thenReturn(true) - val clickIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } + val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } val expandable = mock<Expandable>() underTest.startClickIntent(expandable, clickIntent) @@ -129,7 +133,7 @@ class MediaControlInteractorTest : SysuiTestCase() { fun startClickIntent_hideOverLockscreen() { whenever(keyguardStateController.isShowing).thenReturn(false) - val clickIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } + val clickIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } val expandable = mock<Expandable>() val activityController = mock<ActivityTransitionAnimator.Controller>() whenever(expandable.activityTransitionController(any())).thenReturn(activityController) @@ -146,7 +150,7 @@ class MediaControlInteractorTest : SysuiTestCase() { whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())) .thenReturn(true) - val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } + val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } underTest.startDeviceIntent(deviceIntent) @@ -159,7 +163,7 @@ class MediaControlInteractorTest : SysuiTestCase() { whenever(kosmos.activityIntentHelper.wouldPendingShowOverLockscreen(any(), any())) .thenReturn(true) - val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(false) } + val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(false) } underTest.startDeviceIntent(deviceIntent) @@ -170,7 +174,7 @@ class MediaControlInteractorTest : SysuiTestCase() { fun startDeviceIntent_hideOverLockscreen() { whenever(keyguardStateController.isShowing).thenReturn(false) - val deviceIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(true) } + val deviceIntent = mock<PendingIntent> { whenever(it.isActivity).thenReturn(true) } underTest.startDeviceIntent(deviceIntent) @@ -191,6 +195,7 @@ class MediaControlInteractorTest : SysuiTestCase() { eq(PACKAGE_NAME), eq(true), eq(dialogTransitionController), + eq(null), eq(null) ) } @@ -211,6 +216,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..0551bfb89865 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.onReorderingAllowed() + + 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/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt new file mode 100644 index 000000000000..9d8ec951dfe7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModelTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notifications.ui.viewmodel + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.Swipe +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ui.viewmodel.notificationsShadeSceneViewModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +@EnableSceneContainer +class NotificationsShadeSceneViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } + + private val underTest = kosmos.notificationsShadeSceneViewModel + + @Test + fun upTransitionSceneKey_deviceLocked_lockscreen() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + lockDevice() + + assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun upTransitionSceneKey_deviceUnlocked_gone() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + lockDevice() + unlockDevice() + + assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone) + } + + @Test + fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + + assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + runCurrent() + sceneInteractor.changeScene(Scenes.Gone, "reason") + + assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone) + } + + private fun TestScope.lockDevice() { + val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + runCurrent() + } + + private fun TestScope.unlockDevice() { + val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + sceneInteractor.changeScene(Scenes.Gone, "reason") + runCurrent() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/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/panels/data/repository/IconAndNameCustomRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt new file mode 100644 index 000000000000..1e5599bfe1d5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryTest.kt @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import android.content.ComponentName +import android.content.packageManager +import android.content.pm.PackageManager +import android.content.pm.ServiceInfo +import android.content.pm.UserInfo +import android.graphics.drawable.TestStubDrawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.kosmos.mainCoroutineContext +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.shared.model.EditTileData +import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository +import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository +import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.settings.fakeUserTracker +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class IconAndNameCustomRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val packageManager: PackageManager = kosmos.packageManager + private val userTracker: FakeUserTracker = + kosmos.fakeUserTracker.apply { + whenever(userContext.packageManager).thenReturn(packageManager) + } + + private val service1 = + FakeInstalledTilesComponentRepository.ServiceInfo( + component1, + tileService1, + drawable1, + appName1, + ) + + private val service2 = + FakeInstalledTilesComponentRepository.ServiceInfo( + component2, + tileService2, + drawable2, + appName2, + ) + + private val underTest = + with(kosmos) { + IconAndNameCustomRepository( + installedTilesRepository, + userTracker, + mainCoroutineContext, + ) + } + + @Before + fun setUp() { + kosmos.fakeInstalledTilesRepository.setInstalledServicesForUser( + userTracker.userId, + listOf(service1, service2) + ) + } + + @Test + fun loadDataForCurrentServices() = + with(kosmos) { + testScope.runTest { + val editTileDataList = underTest.getCustomTileData() + val expectedData1 = + EditTileData( + TileSpec.create(component1), + Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)), + Text.Loaded(tileService1), + Text.Loaded(appName1), + ) + val expectedData2 = + EditTileData( + TileSpec.create(component2), + Icon.Loaded(drawable2, ContentDescription.Loaded(tileService2)), + Text.Loaded(tileService2), + Text.Loaded(appName2), + ) + + assertThat(editTileDataList).containsExactly(expectedData1, expectedData2) + } + } + + @Test + fun loadDataForCurrentServices_otherCurrentUser_empty() = + with(kosmos) { + testScope.runTest { + userTracker.set(listOf(UserInfo(11, "", 0)), 0) + val editTileDataList = underTest.getCustomTileData() + + assertThat(editTileDataList).isEmpty() + } + } + + @Test + fun loadDataForCurrentServices_serviceInfoWithNullIcon_notInList() = + with(kosmos) { + testScope.runTest { + val serviceNullIcon = + FakeInstalledTilesComponentRepository.ServiceInfo( + component2, + tileService2, + ) + fakeInstalledTilesRepository.setInstalledServicesForUser( + userTracker.userId, + listOf(service1, serviceNullIcon) + ) + + val expectedData1 = + EditTileData( + TileSpec.create(component1), + Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1)), + Text.Loaded(tileService1), + Text.Loaded(appName1), + ) + + val editTileDataList = underTest.getCustomTileData() + assertThat(editTileDataList).containsExactly(expectedData1) + } + } + + private companion object { + val drawable1 = TestStubDrawable("drawable1") + val appName1 = "App1" + val tileService1 = "Tile Service 1" + val component1 = ComponentName("pkg1", "srv1") + + val drawable2 = TestStubDrawable("drawable2") + val appName2 = "App2" + val tileService2 = "Tile Service 2" + val component2 = ComponentName("pkg2", "srv2") + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryTest.kt new file mode 100644 index 000000000000..56cead19d1df --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryTest.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import android.content.res.mainResources +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.res.R +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class StockTilesRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + + private val underTest = StockTilesRepository(kosmos.mainResources) + + @Test + fun stockTilesMatchesResources() { + val expected = + kosmos.mainResources + .getString(R.string.quick_settings_tiles_stock) + .split(",") + .map(TileSpec::create) + assertThat(underTest.stockTiles).isEqualTo(expected) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt new file mode 100644 index 000000000000..deefbf585ba9 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorTest.kt @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.content.ComponentName +import android.graphics.drawable.TestStubDrawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.data.repository.iconAndNameCustomRepository +import com.android.systemui.qs.panels.data.repository.stockTilesRepository +import com.android.systemui.qs.panels.shared.model.EditTileData +import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository +import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig +import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig +import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.fakeQSTileConfigProvider +import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider +import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class EditTilesListInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + + // Only have some configurations so we can test the effect of missing configurations. + // As the configurations are injected by dagger, we'll have all the existing configurations + private val internetTileConfig = kosmos.qsInternetTileConfig + private val flashlightTileConfig = kosmos.qsFlashlightTileConfig + private val batteryTileConfig = kosmos.qsBatterySaverTileConfig + + private val serviceInfo = + FakeInstalledTilesComponentRepository.ServiceInfo( + component, + tileName, + icon, + appName, + ) + + private val underTest = + with(kosmos) { + EditTilesListInteractor( + stockTilesRepository, + qSTileConfigProvider, + iconAndNameCustomRepository, + ) + } + + @Before + fun setUp() { + with(kosmos) { + fakeInstalledTilesRepository.setInstalledServicesForUser( + userTracker.userId, + listOf(serviceInfo) + ) + + with(fakeQSTileConfigProvider) { + putConfig(internetTileConfig.tileSpec, internetTileConfig) + putConfig(flashlightTileConfig.tileSpec, flashlightTileConfig) + putConfig(batteryTileConfig.tileSpec, batteryTileConfig) + } + } + } + + @Test + fun getTilesToEdit_stockTilesHaveNoAppName() = + with(kosmos) { + testScope.runTest { + val editTiles = underTest.getTilesToEdit() + + assertThat(editTiles.stockTiles.all { it.appName == null }).isTrue() + } + } + + @Test + fun getTilesToEdit_stockTilesAreAllPlatformSpecs() = + with(kosmos) { + testScope.runTest { + val editTiles = underTest.getTilesToEdit() + + assertThat(editTiles.stockTiles.all { it.tileSpec is TileSpec.PlatformTileSpec }) + .isTrue() + } + } + + @Test + fun getTilesToEdit_stockTiles_sameOrderAsRepository() = + with(kosmos) { + testScope.runTest { + val editTiles = underTest.getTilesToEdit() + + assertThat(editTiles.stockTiles.map { it.tileSpec }) + .isEqualTo(stockTilesRepository.stockTiles) + } + } + + @Test + fun getTilesToEdit_customTileData_matchesService() = + with(kosmos) { + testScope.runTest { + val editTiles = underTest.getTilesToEdit() + val expected = + EditTileData( + tileSpec = TileSpec.create(component), + icon = Icon.Loaded(icon, ContentDescription.Loaded(tileName)), + label = Text.Loaded(tileName), + appName = Text.Loaded(appName), + ) + + assertThat(editTiles.customTiles).hasSize(1) + assertThat(editTiles.customTiles[0]).isEqualTo(expected) + } + } + + @Test + fun getTilesToEdit_tilesInConfigProvider_correctData() = + with(kosmos) { + testScope.runTest { + val editTiles = underTest.getTilesToEdit() + + assertThat( + editTiles.stockTiles.first { it.tileSpec == internetTileConfig.tileSpec } + ) + .isEqualTo(internetTileConfig.toEditTileData()) + assertThat( + editTiles.stockTiles.first { it.tileSpec == flashlightTileConfig.tileSpec } + ) + .isEqualTo(flashlightTileConfig.toEditTileData()) + assertThat(editTiles.stockTiles.first { it.tileSpec == batteryTileConfig.tileSpec }) + .isEqualTo(batteryTileConfig.toEditTileData()) + } + } + + @Test + fun getTilesToEdit_tilesNotInConfigProvider_useDefaultData() = + with(kosmos) { + testScope.runTest { + underTest + .getTilesToEdit() + .stockTiles + .filterNot { qSTileConfigProvider.hasConfig(it.tileSpec.spec) } + .forEach { assertThat(it).isEqualTo(it.tileSpec.missingConfigEditTileData()) } + } + } + + private companion object { + val component = ComponentName("pkg", "srv") + const val tileName = "Tile Service" + const val appName = "App" + val icon = TestStubDrawable("icon") + + fun TileSpec.missingConfigEditTileData(): EditTileData { + return EditTileData( + tileSpec = this, + icon = Icon.Resource(android.R.drawable.star_on, ContentDescription.Loaded(spec)), + label = Text.Loaded(spec), + appName = null + ) + } + + fun QSTileConfig.toEditTileData(): EditTileData { + return EditTileData( + tileSpec = tileSpec, + icon = + Icon.Resource(uiConfig.iconRes, ContentDescription.Resource(uiConfig.labelRes)), + label = Text.Resource(uiConfig.labelRes), + appName = null, + ) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt new file mode 100644 index 000000000000..9fb25a28415b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelTest.kt @@ -0,0 +1,507 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.R +import android.content.ComponentName +import android.graphics.drawable.TestStubDrawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.FakeQSTile +import com.android.systemui.qs.panels.data.repository.stockTilesRepository +import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor +import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap +import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor +import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout +import com.android.systemui.qs.panels.shared.model.EditTileData +import com.android.systemui.qs.pipeline.data.repository.FakeInstalledTilesComponentRepository +import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository +import com.android.systemui.qs.pipeline.data.repository.fakeInstalledTilesRepository +import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository +import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor +import com.android.systemui.qs.pipeline.domain.interactor.minimumTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.qsTileFactory +import com.android.systemui.qs.tiles.impl.alarm.qsAlarmTileConfig +import com.android.systemui.qs.tiles.impl.battery.qsBatterySaverTileConfig +import com.android.systemui.qs.tiles.impl.flashlight.qsFlashlightTileConfig +import com.android.systemui.qs.tiles.impl.internet.qsInternetTileConfig +import com.android.systemui.qs.tiles.impl.sensorprivacy.qsCameraSensorPrivacyToggleTileConfig +import com.android.systemui.qs.tiles.impl.sensorprivacy.qsMicrophoneSensorPrivacyToggleTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.fakeQSTileConfigProvider +import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider +import com.android.systemui.settings.userTracker +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@SmallTest +class EditModeViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + + // Only have some configurations so we can test the effect of missing configurations. + // As the configurations are injected by dagger, we'll have all the existing configurations + private val configs = + with(kosmos) { + setOf( + qsInternetTileConfig, + qsFlashlightTileConfig, + qsBatterySaverTileConfig, + qsAlarmTileConfig, + qsCameraSensorPrivacyToggleTileConfig, + qsMicrophoneSensorPrivacyToggleTileConfig, + ) + } + + private val serviceInfo1 = + FakeInstalledTilesComponentRepository.ServiceInfo( + component1, + tileService1, + drawable1, + appName1, + ) + + private val serviceInfo2 = + FakeInstalledTilesComponentRepository.ServiceInfo( + component2, + tileService2, + drawable2, + appName2, + ) + + private val underTest: EditModeViewModel by lazy { + with(kosmos) { + EditModeViewModel( + editTilesListInteractor, + currentTilesInteractor, + minimumTilesInteractor, + infiniteGridLayout, + applicationCoroutineScope, + gridLayoutTypeInteractor, + gridLayoutMap, + ) + } + } + + @Before + fun setUp() { + with(kosmos) { + fakeMinimumTilesRepository = MinimumTilesFixedRepository(minNumberOfTiles) + + fakeInstalledTilesRepository.setInstalledServicesForUser( + userTracker.userId, + listOf(serviceInfo1, serviceInfo2) + ) + + with(fakeQSTileConfigProvider) { configs.forEach { putConfig(it.tileSpec, it) } } + qsTileFactory = FakeQSFactory { FakeQSTile(userTracker.userId, available = true) } + } + } + + @Test + fun isEditing() = + with(kosmos) { + testScope.runTest { + val isEditing by collectLastValue(underTest.isEditing) + + assertThat(isEditing).isFalse() + + underTest.startEditing() + assertThat(isEditing).isTrue() + + underTest.stopEditing() + assertThat(isEditing).isFalse() + } + } + + @Test + fun editing_false_emptyFlowOfTiles() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + assertThat(tiles).isNull() + } + } + + @Test + fun editing_true_notEmptyTileData() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + underTest.startEditing() + + assertThat(tiles).isNotEmpty() + } + } + + @Test + fun tilesData_hasAllStockTiles() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + underTest.startEditing() + + assertThat( + tiles!! + .filter { it.tileSpec is TileSpec.PlatformTileSpec } + .map { it.tileSpec } + ) + .containsExactlyElementsIn(stockTilesRepository.stockTiles) + } + } + + @Test + fun tilesData_stockTiles_haveCorrectUiValues() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + underTest.startEditing() + + tiles!! + .filter { it.tileSpec is TileSpec.PlatformTileSpec } + .forEach { + val data = getEditTileData(it.tileSpec) + + assertThat(it.label).isEqualTo(data.label) + assertThat(it.icon).isEqualTo(data.icon) + assertThat(it.appName).isNull() + } + } + } + + @Test + fun tilesData_hasAllCustomTiles() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + underTest.startEditing() + + assertThat( + tiles!! + .filter { it.tileSpec is TileSpec.CustomTileSpec } + .map { it.tileSpec } + ) + .containsExactly(TileSpec.create(component1), TileSpec.create(component2)) + } + } + + @Test + fun tilesData_customTiles_haveCorrectUiValues() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + + underTest.startEditing() + + // service1 + val model1 = tiles!!.first { it.tileSpec == TileSpec.create(component1) } + assertThat(model1.label).isEqualTo(Text.Loaded(tileService1)) + assertThat(model1.appName).isEqualTo(Text.Loaded(appName1)) + assertThat(model1.icon) + .isEqualTo(Icon.Loaded(drawable1, ContentDescription.Loaded(tileService1))) + + // service2 + val model2 = tiles!!.first { it.tileSpec == TileSpec.create(component2) } + assertThat(model2.label).isEqualTo(Text.Loaded(tileService2)) + assertThat(model2.appName).isEqualTo(Text.Loaded(appName2)) + assertThat(model2.icon) + .isEqualTo(Icon.Loaded(drawable2, ContentDescription.Loaded(tileService2))) + } + } + + @Test + fun currentTiles_inCorrectOrder_markedAsCurrent() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + listOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + TileSpec.create(component2), + TileSpec.create("alarm"), + ) + currentTilesInteractor.setTiles(currentTiles) + + underTest.startEditing() + + assertThat(tiles!!.filter { it.isCurrent }.map { it.tileSpec }) + .containsExactlyElementsIn(currentTiles) + .inOrder() + } + } + + @Test + fun notCurrentTiles() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + listOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + TileSpec.create(component2), + TileSpec.create("alarm"), + ) + val remainingTiles = + stockTilesRepository.stockTiles.filterNot { it in currentTiles } + + listOf(TileSpec.create(component1)) + currentTilesInteractor.setTiles(currentTiles) + + underTest.startEditing() + + assertThat(tiles!!.filterNot { it.isCurrent }.map { it.tileSpec }) + .containsExactlyElementsIn(remainingTiles) + } + } + + @Test + fun currentTilesChange_trackingChange() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + mutableListOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + TileSpec.create(component2), + TileSpec.create("alarm"), + ) + currentTilesInteractor.setTiles(currentTiles) + + underTest.startEditing() + + val newTile = TileSpec.create("internet") + val position = 1 + currentTilesInteractor.addTile(newTile, position) + currentTiles.add(position, newTile) + + assertThat(tiles!!.filter { it.isCurrent }.map { it.tileSpec }) + .containsExactlyElementsIn(currentTiles) + .inOrder() + } + } + + @Test + fun nonCurrentTiles_orderPreservedWhenCurrentTilesChange() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + mutableListOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + TileSpec.create(component2), + TileSpec.create("alarm"), + ) + currentTilesInteractor.setTiles(currentTiles) + + underTest.startEditing() + + val nonCurrentSpecs = tiles!!.filterNot { it.isCurrent }.map { it.tileSpec } + val newTile = TileSpec.create("internet") + currentTilesInteractor.addTile(newTile) + + assertThat(tiles!!.filterNot { it.isCurrent }.map { it.tileSpec }) + .containsExactlyElementsIn(nonCurrentSpecs - listOf(newTile)) + .inOrder() + } + } + + @Test + fun nonCurrentTiles_haveOnlyAddAction() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + mutableListOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + TileSpec.create(component2), + TileSpec.create("alarm"), + ) + currentTilesInteractor.setTiles(currentTiles) + + underTest.startEditing() + + tiles!! + .filterNot { it.isCurrent } + .forEach { + assertThat(it.availableEditActions) + .containsExactly(AvailableEditActions.ADD) + } + } + } + + @Test + fun currentTiles_moreThanMinimumTiles_haveRemoveAction() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + mutableListOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + TileSpec.create(component2), + TileSpec.create("alarm"), + ) + currentTilesInteractor.setTiles(currentTiles) + assertThat(currentTiles.size).isGreaterThan(minNumberOfTiles) + + underTest.startEditing() + + tiles!! + .filter { it.isCurrent } + .forEach { + assertThat(it.availableEditActions).contains(AvailableEditActions.REMOVE) + } + } + } + + @Test + fun currentTiles_minimumTiles_dontHaveRemoveAction() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + mutableListOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + TileSpec.create(component2), + ) + currentTilesInteractor.setTiles(currentTiles) + assertThat(currentTiles.size).isEqualTo(minNumberOfTiles) + + underTest.startEditing() + + tiles!! + .filter { it.isCurrent } + .forEach { + assertThat(it.availableEditActions) + .doesNotContain(AvailableEditActions.REMOVE) + } + } + } + + @Test + fun currentTiles_lessThanMinimumTiles_dontHaveRemoveAction() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + mutableListOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + ) + currentTilesInteractor.setTiles(currentTiles) + assertThat(currentTiles.size).isLessThan(minNumberOfTiles) + + underTest.startEditing() + + tiles!! + .filter { it.isCurrent } + .forEach { + assertThat(it.availableEditActions) + .doesNotContain(AvailableEditActions.REMOVE) + } + } + } + + @Test + fun currentTiles_haveMoveAction() = + with(kosmos) { + testScope.runTest { + val tiles by collectLastValue(underTest.tiles) + val currentTiles = + mutableListOf( + TileSpec.create("flashlight"), + TileSpec.create("airplane"), + TileSpec.create(component2), + TileSpec.create("alarm"), + ) + currentTilesInteractor.setTiles(currentTiles) + + underTest.startEditing() + + tiles!! + .filter { it.isCurrent } + .forEach { + assertThat(it.availableEditActions).contains(AvailableEditActions.MOVE) + } + } + } + + private companion object { + val drawable1 = TestStubDrawable("drawable1") + val appName1 = "App1" + val tileService1 = "Tile Service 1" + val component1 = ComponentName("pkg1", "srv1") + + val drawable2 = TestStubDrawable("drawable2") + val appName2 = "App2" + val tileService2 = "Tile Service 2" + val component2 = ComponentName("pkg2", "srv2") + + fun TileSpec.missingConfigEditTileData(): EditTileData { + return EditTileData( + tileSpec = this, + icon = Icon.Resource(R.drawable.star_on, ContentDescription.Loaded(spec)), + label = Text.Loaded(spec), + appName = null + ) + } + + fun QSTileConfig.toEditTileData(): EditTileData { + return EditTileData( + tileSpec = tileSpec, + icon = + Icon.Resource(uiConfig.iconRes, ContentDescription.Resource(uiConfig.labelRes)), + label = Text.Resource(uiConfig.labelRes), + appName = null, + ) + } + + fun Kosmos.getEditTileData(tileSpec: TileSpec): EditTileData { + return if (qSTileConfigProvider.hasConfig(tileSpec.spec)) { + qSTileConfigProvider.getConfig(tileSpec.spec).toEditTileData() + } else { + tileSpec.missingConfigEditTileData() + } + } + + val minNumberOfTiles = 3 + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt index bc57ce6f95f5..a0dec8cb745d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt @@ -90,7 +90,7 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { } @Test - fun componentsLoadedOnStart() = + fun servicesLoadedOnStart() = testScope.runTest { val userId = 0 val resolveInfo = @@ -106,12 +106,14 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) runCurrent() + val services = underTest.getInstalledTilesServiceInfos(userId) assertThat(componentNames).containsExactly(TEST_COMPONENT) + assertThat(services).containsExactly(resolveInfo.serviceInfo) } @Test - fun componentAdded_foundAfterPackageChange() = + fun serviceAdded_foundAfterPackageChange() = testScope.runTest { val userId = 0 val resolveInfo = @@ -132,12 +134,14 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { .thenReturn(listOf(resolveInfo)) kosmos.fakePackageChangeRepository.notifyChange(PackageChangeModel.Empty) runCurrent() + val services = underTest.getInstalledTilesServiceInfos(userId) assertThat(componentNames).containsExactly(TEST_COMPONENT) + assertThat(services).containsExactly(resolveInfo.serviceInfo) } @Test - fun componentWithoutPermission_notValid() = + fun serviceWithoutPermission_notValid() = testScope.runTest { val userId = 0 val resolveInfo = @@ -152,13 +156,15 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { .thenReturn(listOf(resolveInfo)) val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) + val services = underTest.getInstalledTilesServiceInfos(userId) runCurrent() assertThat(componentNames).isEmpty() + assertThat(services).isEmpty() } @Test - fun componentNotEnabled_notValid() = + fun serviceNotEnabled_notValid() = testScope.runTest { val userId = 0 val resolveInfo = @@ -173,9 +179,11 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { .thenReturn(listOf(resolveInfo)) val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) + val services = underTest.getInstalledTilesServiceInfos(userId) runCurrent() assertThat(componentNames).isEmpty() + assertThat(services).isEmpty() } @Test @@ -221,30 +229,22 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) runCurrent() + val service = underTest.getInstalledTilesServiceInfos(userId) assertThat(componentNames).containsExactly(TEST_COMPONENT) + assertThat(service).containsExactly(resolveInfo.serviceInfo) } @Test - fun loadComponentsForSameUserTwice_returnsSameFlow() = + fun loadServicesForSameUserTwice_returnsSameFlow() = testScope.runTest { - val flowForUser1 = underTest.getInstalledTilesComponents(1) - val flowForUser1TheSecondTime = underTest.getInstalledTilesComponents(1) + val flowForUser1 = underTest.getInstalledTilesServiceInfos(1) + val flowForUser1TheSecondTime = underTest.getInstalledTilesServiceInfos(1) runCurrent() assertThat(flowForUser1TheSecondTime).isEqualTo(flowForUser1) } - @Test - fun loadComponentsForDifferentUsers_returnsDifferentFlow() = - testScope.runTest { - val flowForUser1 = underTest.getInstalledTilesComponents(1) - val flowForUser2 = underTest.getInstalledTilesComponents(2) - runCurrent() - - assertThat(flowForUser2).isNotEqualTo(flowForUser1) - } - // Tests that a ServiceInfo that is returned by queryIntentServicesAsUser but shortly // after uninstalled, doesn't crash SystemUI. @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt index 61e4774d9c92..3faab5048fb4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AccessibilityTilesInteractorTest.kt @@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.pipeline.domain.model.TileModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt index 8ae917264a37..167eff193147 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dump.DumpManager +import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository import com.android.systemui.qs.pipeline.domain.autoaddable.FakeAutoAddable import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index 634c5fa74295..1c73fe2b305d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.dump.nano.SystemUIProtoDump import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTile.BooleanState import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.external.CustomTile import com.android.systemui.qs.external.CustomTileStatePersister import com.android.systemui.qs.external.TileLifecycleManager diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt index 90c83047e72f..260189d401d2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.data.repository.FakeDefaultTilesRepository import com.android.systemui.qs.pipeline.data.repository.MinimumTilesFixedRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt index 4207a9c27ad0..dffd0d72969c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.FakeQSFactory +import com.android.systemui.qs.FakeQSTile import com.android.systemui.qs.pipeline.data.model.RestoreData import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository @@ -54,9 +55,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() { - private val kosmos by lazy { - Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } - } + private val kosmos by lazy { Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) } } // Getter here so it can change when there is a managed profile. private val workTileAvailable: Boolean get() = hasManagedProfile() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt index bf48784407b8..02a81419ea78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerTest.kt @@ -69,6 +69,15 @@ class QSTileIntentUserInputHandlerTest : SysuiTestCase() { } @Test + fun testPassesIntentToStarter_dismissShadeAndShowOverLockScreenWhenLocked() { + val intent = Intent("test.ACTION") + + underTest.handle(null, intent, true) + + verify(activityStarter).startActivity(eq(intent), eq(true), any(), eq(true)) + } + + @Test fun testPassesActivityPendingIntentToStarterAsPendingIntent() { val pendingIntent = mock<PendingIntent> { whenever(isActivity).thenReturn(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/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt index 182a6040b3b3..d3095542e90f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingUserActionInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor +import android.content.Context import android.provider.Settings import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -23,6 +24,8 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable +import com.android.systemui.animation.LaunchableView import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.ActivityStarter @@ -36,7 +39,6 @@ import com.android.systemui.statusbar.phone.SystemUIDialog 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.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth import kotlinx.coroutines.test.runTest @@ -63,6 +65,8 @@ class FontScalingUserActionInteractorTest : SysuiTestCase() { @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator @Mock private lateinit var dialog: SystemUIDialog @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var expandable: Expandable + @Mock private lateinit var controller: DialogTransitionAnimator.Controller @Captor private lateinit var argumentCaptor: ArgumentCaptor<Runnable> @@ -73,6 +77,9 @@ class FontScalingUserActionInteractorTest : SysuiTestCase() { dialog = mock<SystemUIDialog>() fontScalingDialogDelegate = mock<FontScalingDialogDelegate> { whenever(createDialog()).thenReturn(dialog) } + controller = mock<DialogTransitionAnimator.Controller>() + expandable = + mock<Expandable> { whenever(dialogTransitionController(any())).thenReturn(controller) } argumentCaptor = ArgumentCaptor.forClass(Runnable::class.java) underTest = @@ -90,9 +97,8 @@ class FontScalingUserActionInteractorTest : SysuiTestCase() { fun clickTile_screenUnlocked_showDialogAnimationFromView() = kosmos.testScope.runTest { keyguardStateController.isShowing = false - val testView = View(context) - underTest.handleInput(click(FontScalingTileModel, view = testView)) + underTest.handleInput(click(FontScalingTileModel, expandable = expandable)) verify(activityStarter) .executeRunnableDismissingKeyguard( @@ -103,17 +109,15 @@ class FontScalingUserActionInteractorTest : SysuiTestCase() { eq(false) ) argumentCaptor.value.run() - verify(mDialogTransitionAnimator) - .showFromView(any(), eq(testView), nullable(), anyBoolean()) + verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean()) } @Test fun clickTile_onLockScreen_neverShowDialogAnimationFromView_butShowsDialog() = kosmos.testScope.runTest { keyguardStateController.isShowing = true - val testView = View(context) - underTest.handleInput(click(FontScalingTileModel, view = testView)) + underTest.handleInput(click(FontScalingTileModel, expandable = expandable)) verify(activityStarter) .executeRunnableDismissingKeyguard( @@ -124,8 +128,7 @@ class FontScalingUserActionInteractorTest : SysuiTestCase() { eq(false) ) argumentCaptor.value.run() - verify(mDialogTransitionAnimator, never()) - .showFromView(any(), eq(testView), nullable(), anyBoolean()) + verify(mDialogTransitionAnimator, never()).show(any(), any(), anyBoolean()) verify(dialog).show() } @@ -140,4 +143,8 @@ class FontScalingUserActionInteractorTest : SysuiTestCase() { val expectedIntentAction = Settings.ACTION_TEXT_READING_SETTINGS Truth.assertThat(actualIntentAction).isEqualTo(expectedIntentAction) } + + private class FontScalingTileTestView(context: Context) : View(context), LaunchableView { + override fun setShouldBlockVisibilityChanges(block: Boolean) {} + } } 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/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt new file mode 100644 index 000000000000..0761ee734830 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileDataInteractorTest.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.onehanded.domain.interactor + +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.accessibility.data.repository.oneHandedModeRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileDataInteractor +import com.android.wm.shell.onehanded.OneHanded +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class OneHandedModeTileDataInteractorTest : SysuiTestCase() { + + private val kosmos = Kosmos() + private val testUser = UserHandle.of(1)!! + private val oneHandedModeRepository = kosmos.oneHandedModeRepository + private val underTest: OneHandedModeTileDataInteractor = + OneHandedModeTileDataInteractor(oneHandedModeRepository) + + @Test + fun availability_matchesController() = runTest { + val expectedAvailability = OneHanded.sIsSupportOneHandedMode + val availability by collectLastValue(underTest.availability(testUser)) + + assertThat(availability).isEqualTo(expectedAvailability) + } + + @Test + fun data_matchesRepository() = runTest { + val lastData by + collectLastValue(underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))) + runCurrent() + assertThat(lastData!!.isEnabled).isFalse() + + oneHandedModeRepository.setIsEnabled(true, testUser) + runCurrent() + assertThat(lastData!!.isEnabled).isTrue() + + oneHandedModeRepository.setIsEnabled(false, testUser) + runCurrent() + assertThat(lastData!!.isEnabled).isFalse() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..3f17d4cec643 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/domain/interactor/OneHandedModeTileUserActionInteractorTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.onehanded.domain.interactor + +import android.os.UserHandle +import android.platform.test.annotations.EnabledOnRavenwood +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.accessibility.data.repository.FakeOneHandedModeRepository +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.onehanded.domain.OneHandedModeTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class OneHandedModeTileUserActionInteractorTest : SysuiTestCase() { + + private val testUser = UserHandle.of(1) + private val repository = FakeOneHandedModeRepository() + private val inputHandler = FakeQSTileIntentUserInputHandler() + + private val underTest = + OneHandedModeTileUserActionInteractor( + repository, + inputHandler, + ) + + @Test + fun handleClickWhenEnabled() = runTest { + val wasEnabled = true + repository.setIsEnabled(wasEnabled, testUser) + + underTest.handleInput( + QSTileInputTestKtx.click(OneHandedModeTileModel(wasEnabled), testUser) + ) + + assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled) + } + + @Test + fun handleClickWhenDisabled() = runTest { + val wasEnabled = false + repository.setIsEnabled(wasEnabled, testUser) + + underTest.handleInput( + QSTileInputTestKtx.click(OneHandedModeTileModel(wasEnabled), testUser) + ) + + assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled) + } + + @Test + fun handleLongClickWhenDisabled() = runTest { + val enabled = false + + underTest.handleInput( + QSTileInputTestKtx.longClick(OneHandedModeTileModel(enabled), testUser) + ) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_ONE_HANDED_SETTINGS) + } + } + + @Test + fun handleLongClickWhenEnabled() = runTest { + val enabled = true + + underTest.handleInput( + QSTileInputTestKtx.longClick(OneHandedModeTileModel(enabled), testUser) + ) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_ONE_HANDED_SETTINGS) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt new file mode 100644 index 000000000000..7ef020da8b67 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.onehanded.ui + +import android.graphics.drawable.TestStubDrawable +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.tileimpl.SubtitleArrayMapping +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel +import com.android.systemui.qs.tiles.impl.onehanded.qsOneHandedModeTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class OneHandedModeTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val config = kosmos.qsOneHandedModeTileConfig + private val subtitleArrayId = SubtitleArrayMapping.getSubtitleId(config.tileSpec.spec) + private val subtitleArray by lazy { context.resources.getStringArray(subtitleArrayId) } + + private lateinit var mapper: OneHandedModeTileMapper + + @Before + fun setup() { + mapper = + OneHandedModeTileMapper( + context.orCreateTestableResources + .apply { + addOverride( + com.android.internal.R.drawable.ic_qs_one_handed_mode, + TestStubDrawable() + ) + } + .resources, + context.theme + ) + } + + @Test + fun disabledModel() { + val inputModel = OneHandedModeTileModel(false) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createOneHandedModeTileState( + QSTileState.ActivationState.INACTIVE, + subtitleArray[1], + com.android.internal.R.drawable.ic_qs_one_handed_mode + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun enabledModel() { + val inputModel = OneHandedModeTileModel(true) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createOneHandedModeTileState( + QSTileState.ActivationState.ACTIVE, + subtitleArray[2], + com.android.internal.R.drawable.ic_qs_one_handed_mode + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createOneHandedModeTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String, + iconRes: Int, + ): QSTileState { + val label = context.getString(R.string.quick_settings_onehanded_label) + return QSTileState( + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + label, + null, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt new file mode 100644 index 000000000000..c41ce2f7854c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractorTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.qr.domain.interactor + +import android.content.Intent +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.coroutines.collectLastValue +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.Callback +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class QRCodeScannerTileDataInteractorTest : SysuiTestCase() { + + private val testUser = UserHandle.of(1)!! + private val testDispatcher = StandardTestDispatcher() + private val scope = TestScope(testDispatcher) + private val testIntent = mock<Intent>() + private val qrCodeScannerController = + mock<QRCodeScannerController> { + whenever(intent).thenReturn(testIntent) + whenever(isAbleToLaunchScannerActivity).thenReturn(false) + } + private val testAvailableModel = QRCodeScannerTileModel.Available(testIntent) + private val testUnavailableModel = QRCodeScannerTileModel.TemporarilyUnavailable + + private val underTest: QRCodeScannerTileDataInteractor = + QRCodeScannerTileDataInteractor( + testDispatcher, + scope.backgroundScope, + qrCodeScannerController, + ) + + @Test + fun availability_matchesController_cameraNotAvailable() = + scope.runTest { + val expectedAvailability = false + whenever(qrCodeScannerController.isCameraAvailable).thenReturn(false) + + val availability by collectLastValue(underTest.availability(testUser)) + + assertThat(availability).isEqualTo(expectedAvailability) + } + + @Test + fun availability_matchesController_cameraIsAvailable() = + scope.runTest { + val expectedAvailability = true + whenever(qrCodeScannerController.isCameraAvailable).thenReturn(true) + + val availability by collectLastValue(underTest.availability(testUser)) + + assertThat(availability).isEqualTo(expectedAvailability) + } + + @Test + fun data_matchesController() = + scope.runTest { + val captor = argumentCaptor<Callback>() + val lastData by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + + verify(qrCodeScannerController).addCallback(captor.capture()) + val callback = captor.value + + assertThat(lastData!!).isEqualTo(testUnavailableModel) + + whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(true) + callback.onQRCodeScannerActivityChanged() + runCurrent() + assertThat(lastData!!).isEqualTo(testAvailableModel) + + whenever(qrCodeScannerController.isAbleToLaunchScannerActivity).thenReturn(false) + callback.onQRCodeScannerActivityChanged() + runCurrent() + assertThat(lastData!!).isEqualTo(testUnavailableModel) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..312f18029570 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractorTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.domain.interactor + +import android.content.Intent +import android.platform.test.annotations.EnabledOnRavenwood +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.impl.qr.qrCodeScannerTileUserActionInteractor +import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class QRCodeScannerTileUserActionInteractorTest : SysuiTestCase() { + val kosmos = Kosmos() + private val inputHandler = kosmos.qsTileIntentUserInputHandler + private val underTest = kosmos.qrCodeScannerTileUserActionInteractor + private val intent = mock<Intent>() + + @Test + fun handleClick_available() = runTest { + val inputModel = QRCodeScannerTileModel.Available(intent) + + underTest.handleInput(QSTileInputTestKtx.click(inputModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + intent + } + } + + @Test + fun handleClick_temporarilyUnavailable() = runTest { + val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable + + underTest.handleInput(QSTileInputTestKtx.click(inputModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs() + } + + @Test + fun handleLongClick_available() = runTest { + val inputModel = QRCodeScannerTileModel.Available(intent) + + underTest.handleInput(QSTileInputTestKtx.longClick(inputModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs() + } + + @Test + fun handleLongClick_temporarilyUnavailable() = runTest { + val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable + + underTest.handleInput(QSTileInputTestKtx.longClick(inputModel)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledNoInputs() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt new file mode 100644 index 000000000000..d26a21365f54 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.qr.ui + +import android.content.Intent +import android.graphics.drawable.TestStubDrawable +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.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.impl.qr.qsQRCodeScannerTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.util.mockito.mock +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class QRCodeScannerTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val config = kosmos.qsQRCodeScannerTileConfig + + private lateinit var mapper: QRCodeScannerTileMapper + + @Before + fun setup() { + mapper = + QRCodeScannerTileMapper( + context.orCreateTestableResources + .apply { + addOverride( + com.android.systemui.res.R.drawable.ic_qr_code_scanner, + TestStubDrawable() + ) + } + .resources, + context.theme + ) + } + + @Test + fun availableModel() { + val mockIntent = mock<Intent>() + val inputModel = QRCodeScannerTileModel.Available(mockIntent) + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createQRCodeScannerTileState( + QSTileState.ActivationState.INACTIVE, + null, + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun temporarilyUnavailableModel() { + val inputModel = QRCodeScannerTileModel.TemporarilyUnavailable + + val outputState = mapper.map(config, inputModel) + + val expectedState = + createQRCodeScannerTileState( + QSTileState.ActivationState.UNAVAILABLE, + context.getString( + com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label + ) + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createQRCodeScannerTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String?, + ): QSTileState { + val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title) + return QSTileState( + { + Icon.Loaded( + context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!, + null + ) + }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK), + label, + null, + QSTileState.SideViewIcon.Chevron, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt index b9321d55e9dc..91f4ea8ffcc0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractorTest.kt @@ -18,12 +18,11 @@ package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor import android.app.Dialog import android.os.UserHandle -import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator -import com.android.systemui.animation.dialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -138,12 +137,17 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { */ @Test fun handleClickFromView_whenDoingNothing_whenKeyguardNotShowing_showDialogFromView() = runTest { - val view = mock<View>() + val expandable = mock<Expandable>() + val controller = mock<DialogTransitionAnimator.Controller>() + whenever(expandable.dialogTransitionController(any())).thenReturn(controller) + kosmos.fakeKeyguardRepository.setKeyguardShowing(false) val recordingModel = ScreenRecordTileModel.DoingNothing - underTest.handleInput(QSTileInputTestKtx.click(recordingModel, UserHandle.CURRENT, view)) + underTest.handleInput( + QSTileInputTestKtx.click(recordingModel, UserHandle.CURRENT, expandable) + ) val onStartRecordingClickedCaptor = argumentCaptor<Runnable>() verify(recordingController) .createScreenRecordDialog( @@ -158,6 +162,6 @@ class ScreenRecordTileUserActionInteractorTest : SysuiTestCase() { verify(keyguardDismissUtil) .executeWhenUnlocked(onDismissActionCaptor.capture(), eq(false), eq(true)) onDismissActionCaptor.value.onDismiss() - verify(dialogTransitionAnimator).showFromView(eq(dialog), eq(view), any(), eq(true)) + verify(dialogTransitionAnimator).show(eq(dialog), eq(controller), eq(true)) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt index f2eb7f44600e..afe7b8f8d50b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterImplTest.kt @@ -257,7 +257,7 @@ class QSSceneAdapterImplTest : SysuiTestCase() { runCurrent() clearInvocations(qsImpl!!) - underTest.setState(QSSceneAdapter.State.UnsquishingQQS(squishiness)) + underTest.setState(QSSceneAdapter.State.UnsquishingQQS { squishiness }) with(qsImpl!!) { verify(this).setQsVisible(true) verify(this) @@ -273,21 +273,56 @@ class QSSceneAdapterImplTest : SysuiTestCase() { } @Test - fun customizing_QS() = + fun customizing_QS_noAnimations() = testScope.runTest { - val customizing by collectLastValue(underTest.isCustomizing) + val customizerState by collectLastValue(underTest.customizerState) underTest.inflate(context) runCurrent() underTest.setState(QSSceneAdapter.State.QS) - assertThat(customizing).isFalse() + assertThat(customizerState).isEqualTo(CustomizerState.Hidden) underTest.setCustomizerShowing(true) - assertThat(customizing).isTrue() + assertThat(customizerState).isEqualTo(CustomizerState.Showing) underTest.setCustomizerShowing(false) - assertThat(customizing).isFalse() + assertThat(customizerState).isEqualTo(CustomizerState.Hidden) + } + + // This matches the calls made by QSCustomizer + @Test + fun customizing_QS_animations_correctStates() = + testScope.runTest { + val customizerState by collectLastValue(underTest.customizerState) + val animatingInDuration = 100L + val animatingOutDuration = 50L + + underTest.inflate(context) + runCurrent() + underTest.setState(QSSceneAdapter.State.QS) + + assertThat(customizerState).isEqualTo(CustomizerState.Hidden) + + // Start showing customizer with animation + underTest.setCustomizerAnimating(true) + underTest.setCustomizerShowing(true, animatingInDuration) + assertThat(customizerState) + .isEqualTo(CustomizerState.AnimatingIntoCustomizer(animatingInDuration)) + + // Finish animation + underTest.setCustomizerAnimating(false) + assertThat(customizerState).isEqualTo(CustomizerState.Showing) + + // Start closing customizer with animation + underTest.setCustomizerAnimating(true) + underTest.setCustomizerShowing(false, animatingOutDuration) + assertThat(customizerState) + .isEqualTo(CustomizerState.AnimatingOutOfCustomizer(animatingOutDuration)) + + // Finish animation + underTest.setCustomizerAnimating(false) + assertThat(customizerState).isEqualTo(CustomizerState.Hidden) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt index ebd65fdcd538..63ce67c5eb28 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/adapter/QSSceneAdapterTest.kt @@ -32,7 +32,7 @@ class QSSceneAdapterTest : SysuiTestCase() { @Test fun expanding_squishiness1() { - assertThat(QSSceneAdapter.State.Expanding(0.3f).squishiness).isEqualTo(1f) + assertThat(QSSceneAdapter.State.Expanding(0.3f).squishiness()).isEqualTo(1f) } @Test @@ -51,14 +51,14 @@ class QSSceneAdapterTest : SysuiTestCase() { @Test fun unsquishingQQS_expansionSameAsQQS() { val squishiness = 0.6f - assertThat(QSSceneAdapter.State.UnsquishingQQS(squishiness).expansion) + assertThat(QSSceneAdapter.State.UnsquishingQQS { squishiness }.expansion) .isEqualTo(QSSceneAdapter.State.QQS.expansion) } @Test fun unsquishingQS_expansionSameAsQS() { val squishiness = 0.6f - assertThat(QSSceneAdapter.State.UnsquishingQS(squishiness).expansion) + assertThat(QSSceneAdapter.State.UnsquishingQS { squishiness }.expansion) .isEqualTo(QSSceneAdapter.State.QS.expansion) } } 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/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt new file mode 100644 index 000000000000..034c2e9b6789 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModelTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.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.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.ui.viewmodel.quickSettingsShadeSceneViewModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +@EnableSceneContainer +class QuickSettingsShadeSceneViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor + + private val underTest = kosmos.quickSettingsShadeSceneViewModel + + @Test + fun upTransitionSceneKey_deviceLocked_lockscreen() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + lockDevice() + + assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun upTransitionSceneKey_deviceUnlocked_gone() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + lockDevice() + unlockDevice() + + assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone) + } + + @Test + fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + + assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() = + testScope.runTest { + val destinationScenes by collectLastValue(underTest.destinationScenes) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + runCurrent() + sceneInteractor.changeScene(Scenes.Gone, "reason") + + assertThat(destinationScenes?.get(Swipe.Up)?.toScene).isEqualTo(Scenes.Gone) + } + + private fun TestScope.lockDevice() { + val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + runCurrent() + } + + private fun TestScope.unlockDevice() { + val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + sceneInteractor.changeScene(Scenes.Gone, "reason") + runCurrent() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index 6643bcb6ecaf..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, @@ -496,7 +497,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { */ private fun getCurrentSceneInUi(): SceneKey { return when (val state = transitionState.value) { - is ObservableTransitionState.Idle -> state.scene + is ObservableTransitionState.Idle -> state.currentScene is ObservableTransitionState.Transition -> state.fromScene } } @@ -558,6 +559,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = getCurrentSceneInUi(), toScene = to, + currentScene = flowOf(to), progress = progressFlow, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt index a45ac9f7d95d..df30c4bf1b5a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt @@ -70,6 +70,9 @@ class SceneContainerRepositoryTest : SysuiTestCase() { underTest.changeScene(Scenes.Shade) assertThat(currentScene).isEqualTo(Scenes.Shade) + + underTest.snapToScene(Scenes.QuickSettings) + assertThat(currentScene).isEqualTo(Scenes.QuickSettings) } @Test(expected = IllegalStateException::class) @@ -79,6 +82,13 @@ class SceneContainerRepositoryTest : SysuiTestCase() { underTest.changeScene(Scenes.Shade) } + @Test(expected = IllegalStateException::class) + fun snapToScene_noSuchSceneInContainer_throws() { + kosmos.sceneKeys = listOf(Scenes.QuickSettings, Scenes.Lockscreen) + val underTest = kosmos.sceneContainerRepository + underTest.snapToScene(Scenes.Shade) + } + @Test fun isVisible() = testScope.runTest { @@ -122,6 +132,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt index c75e297f23c8..e3108ad1b8f1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorTest.kt @@ -45,11 +45,11 @@ class SceneBackInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor - private val sceneContainerStartable = kosmos.sceneContainerStartable - private val authenticationInteractor = kosmos.authenticationInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val sceneContainerStartable by lazy { kosmos.sceneContainerStartable } + private val authenticationInteractor by lazy { kosmos.authenticationInteractor } - private val underTest = kosmos.sceneBackInteractor + private val underTest by lazy { kosmos.sceneBackInteractor } @Test @EnableSceneContainer diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt index c3366ad21e2a..edaa3d373807 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractorTest.kt @@ -152,6 +152,7 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = sceneDataSource.currentScene.value, toScene = Scenes.Shade, + currentScene = flowOf(sceneDataSource.currentScene.value), progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -178,6 +179,7 @@ class SceneContainerOcclusionInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Shade), progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index f58e01ffef27..2fa94effdbd4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -126,6 +126,71 @@ class SceneInteractorTest : SysuiTestCase() { } @Test + fun snapToScene_toUnknownScene_doesNothing() = + testScope.runTest { + val sceneKeys = + listOf( + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Lockscreen, + Scenes.Gone, + Scenes.Communal, + ) + val navigationDistances = + mapOf( + Scenes.Gone to 0, + Scenes.Lockscreen to 0, + Scenes.Communal to 1, + Scenes.Shade to 2, + Scenes.QuickSettings to 3, + ) + kosmos.sceneContainerConfig = + SceneContainerConfig(sceneKeys, Scenes.Lockscreen, navigationDistances) + underTest = kosmos.sceneInteractor + val currentScene by collectLastValue(underTest.currentScene) + val previousScene = currentScene + assertThat(previousScene).isNotEqualTo(Scenes.Bouncer) + underTest.snapToScene(Scenes.Bouncer, "reason") + assertThat(currentScene).isEqualTo(previousScene) + } + + @Test + fun snapToScene() = + testScope.runTest { + underTest = kosmos.sceneInteractor + + val currentScene by collectLastValue(underTest.currentScene) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + + underTest.snapToScene(Scenes.Shade, "reason") + assertThat(currentScene).isEqualTo(Scenes.Shade) + } + + @Test + fun snapToScene_toGoneWhenUnl_doesNotThrow() = + testScope.runTest { + underTest = kosmos.sceneInteractor + + val currentScene by collectLastValue(underTest.currentScene) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + + underTest.snapToScene(Scenes.Gone, "reason") + assertThat(currentScene).isEqualTo(Scenes.Gone) + } + + @Test(expected = IllegalStateException::class) + fun snapToScene_toGoneWhenStillLocked_throws() = + testScope.runTest { + underTest = kosmos.sceneInteractor + underTest.snapToScene(Scenes.Gone, "reason") + } + + @Test fun sceneChanged_inDataSource() = testScope.runTest { underTest = kosmos.sceneInteractor @@ -155,6 +220,7 @@ class SceneInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -195,6 +261,7 @@ class SceneInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = underTest.currentScene.value, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -235,6 +302,7 @@ class SceneInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Shade), progress = flowOf(0.5f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -256,6 +324,7 @@ class SceneInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Shade), progress = flowOf(0.5f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -271,6 +340,7 @@ class SceneInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0.6f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(false), @@ -288,6 +358,7 @@ class SceneInteractorTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Shade), progress = flowOf(0.5f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -299,7 +370,7 @@ class SceneInteractorTest : SysuiTestCase() { assertThat(isTransitionUserInputOngoing).isTrue() - transitionState.value = ObservableTransitionState.Idle(scene = Scenes.Lockscreen) + transitionState.value = ObservableTransitionState.Idle(currentScene = Scenes.Lockscreen) assertThat(isTransitionUserInputOngoing).isFalse() } 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 5779e37cb1ed..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 @@ -135,6 +135,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Gone, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -150,6 +151,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.Gone, + currentScene = flowOf(Scenes.Gone), progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -435,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() @@ -448,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() } @@ -521,6 +523,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Gone, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Lockscreen), progress = flowOf(0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -871,6 +874,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Gone, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -889,6 +893,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.Gone, + currentScene = flowOf(Scenes.Gone), progress = flowOf(0.5f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -1203,6 +1208,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Bouncer, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Bouncer), progress = flowOf(-0.4f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -1225,6 +1231,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Bouncer, + currentScene = flowOf(Scenes.Bouncer), progress = flowOf(0.9f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(false), @@ -1274,6 +1281,7 @@ class SceneContainerStartableTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = fromScene, toScene = toScene, + currentScene = flowOf(fromScene), progress = flowOf(0.5f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), @@ -1283,7 +1291,7 @@ class SceneContainerStartableTest : SysuiTestCase() { transitionStateFlow.value = ObservableTransitionState.Idle( - scene = toScene, + currentScene = toScene, ) runCurrent() verifyAfterTransition?.invoke() 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/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 66f741620e44..d6e3879b899f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -20,7 +20,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; -import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; import static com.google.common.truth.Truth.assertThat; @@ -79,12 +79,12 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; - import java.util.List; import java.util.concurrent.Executor; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper(setAsMainLooper = true) @SmallTest @@ -341,7 +341,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) != 0).isTrue(); assertThat( - (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING) + (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_PRIVACY) != 0) .isTrue(); } @@ -353,7 +353,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); assertThat((mLayoutParameters.getValue().flags & FLAG_SECURE) == 0).isTrue(); assertThat( - (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING) + (mLayoutParameters.getValue().inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_PRIVACY) == 0) .isTrue(); } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt index 6485c475eeb7..851b7b986527 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt @@ -52,9 +52,9 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor - private val sceneInteractor = kosmos.sceneInteractor - private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor + private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val shadeAnimationInteractor by lazy { kosmos.shadeAnimationInteractor } private val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(Scenes.Lockscreen) @@ -166,6 +166,7 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = checkNotNull(currentScene), toScene = toScene, + currentScene = flowOf(checkNotNull(currentScene)), progress = progressFlow, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt index bb40591335f4..aef9163a65ab 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeAnimationInteractorSceneContainerImplTest.kt @@ -55,6 +55,7 @@ class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.QuickSettings, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -78,6 +79,7 @@ class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.QuickSettings, toScene = Scenes.Gone, + currentScene = flowOf(Scenes.Gone), progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -101,6 +103,7 @@ class ShadeAnimationInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.QuickSettings, toScene = Scenes.Gone, + currentScene = flowOf(Scenes.QuickSettings), progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(true), 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/interactor/ShadeInteractorSceneContainerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt index e1908b9bd136..b1bffdbb1bed 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImplTest.kt @@ -69,6 +69,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.QuickSettings, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = MutableStateFlow(.3f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -98,6 +99,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.QuickSettings, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -122,6 +124,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.QuickSettings, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = MutableStateFlow(.3f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -220,6 +223,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = key, + currentScene = flowOf(key), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -258,6 +262,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = key, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Lockscreen), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -292,6 +297,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Gone, toScene = Scenes.QuickSettings, + currentScene = flowOf(Scenes.QuickSettings), progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -315,6 +321,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.QuickSettings, + currentScene = flowOf(Scenes.QuickSettings), progress = MutableStateFlow(.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -341,6 +348,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -396,6 +404,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = key, + currentScene = flowOf(key), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -434,6 +443,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = key, + currentScene = flowOf(key), progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(false), @@ -472,6 +482,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = key, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Lockscreen), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -510,6 +521,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = key, toScene = Scenes.Lockscreen, + currentScene = flowOf(Scenes.Lockscreen), progress = progress, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(false), @@ -547,6 +559,7 @@ class ShadeInteractorSceneContainerImplTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Lockscreen, toScene = Scenes.QuickSettings, + currentScene = flowOf(Scenes.QuickSettings), progress = MutableStateFlow(0f), isInitiatedByUserInput = true, isUserInputOngoing = flowOf(false), 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 07c4b00364ff..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 @@ -16,6 +16,8 @@ package com.android.systemui.shade.domain.startable +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState @@ -37,6 +39,7 @@ import com.android.systemui.scene.shared.model.fakeSceneDataSource import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.ShadeExpansionListener import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any @@ -49,7 +52,6 @@ import kotlinx.coroutines.flow.flowOf 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 platform.test.runner.parameterized.ParameterizedAndroidJunit4 @@ -58,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 } @@ -67,7 +69,7 @@ class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() { private val fakeConfigurationRepository by lazy { kosmos.fakeConfigurationRepository } private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource } - private lateinit var underTest: ShadeStartable + private val underTest: ShadeStartable = kosmos.shadeStartable companion object { @JvmStatic @@ -78,16 +80,12 @@ class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() { } init { - mSetFlagsRule.setFlagsParameterization(flags!!) - } - - @Before - fun setup() { - underTest = kosmos.shadeStartable + mSetFlagsRule.setFlagsParameterization(flags) } @Test - fun hydrateShadeMode() = + @DisableFlags(DualShade.FLAG_NAME) + fun hydrateShadeMode_dualShadeDisabled() = testScope.runTest { overrideResource(R.bool.config_use_split_notification_shade, false) val shadeMode by collectLastValue(shadeInteractor.shadeMode) @@ -105,6 +103,25 @@ class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() { } @Test + @EnableFlags(DualShade.FLAG_NAME) + fun hydrateShadeMode_dualShadeEnabled() = + testScope.runTest { + overrideResource(R.bool.config_use_split_notification_shade, false) + val shadeMode by collectLastValue(shadeInteractor.shadeMode) + + underTest.start() + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) + + overrideResource(R.bool.config_use_split_notification_shade, true) + fakeConfigurationRepository.onAnyConfigurationChange() + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) + + overrideResource(R.bool.config_use_split_notification_shade, false) + fakeConfigurationRepository.onAnyConfigurationChange() + assertThat(shadeMode).isEqualTo(ShadeMode.Dual) + } + + @Test @EnableSceneContainer fun hydrateShadeExpansionStateManager() = testScope.runTest { @@ -161,6 +178,7 @@ class ShadeStartableTest(flags: FlagsParameterization?) : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = checkNotNull(currentScene), toScene = toScene, + currentScene = flowOf(checkNotNull(currentScene)), progress = progressFlow, isInitiatedByUserInput = true, isUserInputOngoing = flowOf(true), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt new file mode 100644 index 000000000000..0ffabd807ba7 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelTest.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.ui.viewmodel + +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper +@EnableSceneContainer +class OverlayShadeViewModelTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor = kosmos.sceneInteractor + private val deviceUnlockedInteractor by lazy { kosmos.deviceUnlockedInteractor } + + private val underTest = kosmos.overlayShadeViewModel + + @Test + fun backgroundScene_deviceLocked_lockscreen() = + testScope.runTest { + val backgroundScene by collectLastValue(underTest.backgroundScene) + + lockDevice() + + assertThat(backgroundScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun backgroundScene_deviceUnlocked_gone() = + testScope.runTest { + val backgroundScene by collectLastValue(underTest.backgroundScene) + + lockDevice() + unlockDevice() + + assertThat(backgroundScene).isEqualTo(Scenes.Gone) + } + + @Test + fun backgroundScene_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() = + testScope.runTest { + val backgroundScene by collectLastValue(underTest.backgroundScene) + val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + runCurrent() + + assertThat(backgroundScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun backgroundScene_authMethodSwipe_lockscreenDismissed_goesToGone() = + testScope.runTest { + val backgroundScene by collectLastValue(underTest.backgroundScene) + val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.None + ) + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + sceneInteractor.changeScene(Scenes.Gone, "reason") + runCurrent() + + assertThat(backgroundScene).isEqualTo(Scenes.Gone) + } + + @Test + fun onScrimClicked_onLockscreen_goesToLockscreen() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + lockDevice() + sceneInteractor.changeScene(Scenes.Bouncer, "reason") + runCurrent() + assertThat(currentScene).isNotEqualTo(Scenes.Lockscreen) + + underTest.onScrimClicked() + + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + } + + @Test + fun onScrimClicked_deviceWasEntered_goesToGone() = + testScope.runTest { + val currentScene by collectLastValue(sceneInteractor.currentScene) + val backgroundScene by collectLastValue(underTest.backgroundScene) + + lockDevice() + unlockDevice() + sceneInteractor.changeScene(Scenes.QuickSettings, "reason") + runCurrent() + assertThat(backgroundScene).isEqualTo(Scenes.Gone) + assertThat(currentScene).isNotEqualTo(Scenes.Gone) + + underTest.onScrimClicked() + + assertThat(currentScene).isEqualTo(Scenes.Gone) + } + + private fun TestScope.lockDevice() { + val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + sceneInteractor.changeScene(Scenes.Lockscreen, "reason") + runCurrent() + } + + private fun TestScope.unlockDevice() { + val deviceUnlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + sceneInteractor.changeScene(Scenes.Gone, "reason") + runCurrent() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/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/NotificationScrimNestedScrollConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt new file mode 100644 index 000000000000..35e4047109d5 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt @@ -0,0 +1,249 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.nestedscroll.NestedScrollSource +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.notifications.ui.composable.NotificationScrimNestedScrollConnection +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { + private var isStarted = false + private var scrimOffset = 0f + private var contentHeight = 0f + private var isCurrentGestureOverscroll = false + + private val scrollConnection = + NotificationScrimNestedScrollConnection( + scrimOffset = { scrimOffset }, + snapScrimOffset = { _ -> }, + animateScrimOffset = { _ -> }, + minScrimOffset = { MIN_SCRIM_OFFSET }, + maxScrimOffset = MAX_SCRIM_OFFSET, + contentHeight = { contentHeight }, + minVisibleScrimHeight = { MIN_VISIBLE_SCRIM_HEIGHT }, + isCurrentGestureOverscroll = { isCurrentGestureOverscroll }, + onStart = { isStarted = true }, + onStop = { isStarted = false }, + ) + + @Test + fun onScrollUp_canStartPreScroll_contentNotExpanded_ignoreScroll() = runTest { + contentHeight = COLLAPSED_CONTENT_HEIGHT + + val offsetConsumed = + scrollConnection.onPreScroll( + available = Offset(x = 0f, y = -1f), + source = NestedScrollSource.Drag, + ) + + assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(isStarted).isEqualTo(false) + } + + @Test + fun onScrollUp_canStartPreScroll_contentExpandedAtMinOffset_ignoreScroll() = runTest { + contentHeight = EXPANDED_CONTENT_HEIGHT + scrimOffset = MIN_SCRIM_OFFSET + + val offsetConsumed = + scrollConnection.onPreScroll( + available = Offset(x = 0f, y = -1f), + source = NestedScrollSource.Drag, + ) + + assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(isStarted).isEqualTo(false) + } + + @Test + fun onScrollUp_canStartPreScroll_contentExpanded_consumeScroll() = runTest { + contentHeight = EXPANDED_CONTENT_HEIGHT + + val availableOffset = Offset(x = 0f, y = -1f) + val offsetConsumed = + scrollConnection.onPreScroll( + available = availableOffset, + source = NestedScrollSource.Drag, + ) + + assertThat(offsetConsumed).isEqualTo(availableOffset) + assertThat(isStarted).isEqualTo(true) + } + + @Test + fun onScrollUp_canStartPreScroll_contentExpanded_consumeScrollWithRemainder() = runTest { + contentHeight = EXPANDED_CONTENT_HEIGHT + scrimOffset = MIN_SCRIM_OFFSET + 1 + + val availableOffset = Offset(x = 0f, y = -2f) + val consumableOffset = Offset(x = 0f, y = -1f) + val offsetConsumed = + scrollConnection.onPreScroll( + available = availableOffset, + source = NestedScrollSource.Drag, + ) + + assertThat(offsetConsumed).isEqualTo(consumableOffset) + assertThat(isStarted).isEqualTo(true) + } + + @Test + fun onScrollUp_canStartPostScroll_ignoreScroll() = runTest { + val offsetConsumed = + scrollConnection.onPostScroll( + consumed = Offset.Zero, + available = Offset(x = 0f, y = -1f), + source = NestedScrollSource.Drag, + ) + + assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(isStarted).isEqualTo(false) + } + + @Test + fun onScrollDown_canStartPreScroll_ignoreScroll() = runTest { + val offsetConsumed = + scrollConnection.onPreScroll( + available = Offset(x = 0f, y = 1f), + source = NestedScrollSource.Drag, + ) + + assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(isStarted).isEqualTo(false) + } + + @Test + fun onScrollDown_canStartPostScroll_consumeScroll() = runTest { + scrimOffset = MIN_SCRIM_OFFSET + + val availableOffset = Offset(x = 0f, y = 1f) + val offsetConsumed = + scrollConnection.onPostScroll( + consumed = Offset.Zero, + available = availableOffset, + source = NestedScrollSource.Drag + ) + + assertThat(offsetConsumed).isEqualTo(availableOffset) + assertThat(isStarted).isEqualTo(true) + } + + @Test + fun onScrollDown_canStartPostScroll_consumeScrollWithRemainder() = runTest { + scrimOffset = MAX_SCRIM_OFFSET - 1 + + val availableOffset = Offset(x = 0f, y = 2f) + val consumableOffset = Offset(x = 0f, y = 1f) + val offsetConsumed = + scrollConnection.onPostScroll( + consumed = Offset.Zero, + available = availableOffset, + source = NestedScrollSource.Drag + ) + + assertThat(offsetConsumed).isEqualTo(consumableOffset) + assertThat(isStarted).isEqualTo(true) + } + + @Test + fun canStartPostScroll_atMaxOffset_ignoreScroll() = runTest { + scrimOffset = MAX_SCRIM_OFFSET + + val offsetConsumed = + scrollConnection.onPostScroll( + consumed = Offset.Zero, + available = Offset(x = 0f, y = 1f), + source = NestedScrollSource.Drag + ) + + assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(isStarted).isEqualTo(false) + } + + @Test + fun canStartPostScroll_externalOverscrollGesture_startButIgnoreScroll() = runTest { + scrimOffset = MAX_SCRIM_OFFSET + isCurrentGestureOverscroll = true + + val offsetConsumed = + scrollConnection.onPostScroll( + consumed = Offset.Zero, + available = Offset(x = 0f, y = 1f), + source = NestedScrollSource.Drag + ) + + assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(isStarted).isEqualTo(true) + } + + @Test + fun canContinueScroll_inBetweenMinMaxOffset_true() = runTest { + scrimOffset = (MIN_SCRIM_OFFSET + MAX_SCRIM_OFFSET) / 2f + contentHeight = EXPANDED_CONTENT_HEIGHT + scrollConnection.onPreScroll( + available = Offset(x = 0f, y = -1f), + source = NestedScrollSource.Drag + ) + + assertThat(isStarted).isEqualTo(true) + + scrollConnection.onPreScroll( + available = Offset(x = 0f, y = 1f), + source = NestedScrollSource.Drag + ) + + assertThat(isStarted).isEqualTo(true) + } + + @Test + fun canContinueScroll_atMaxOffset_false() = runTest { + scrimOffset = MAX_SCRIM_OFFSET + contentHeight = EXPANDED_CONTENT_HEIGHT + scrollConnection.onPreScroll( + available = Offset(x = 0f, y = -1f), + source = NestedScrollSource.Drag + ) + + assertThat(isStarted).isEqualTo(true) + + scrollConnection.onPreScroll( + available = Offset(x = 0f, y = 1f), + source = NestedScrollSource.Drag + ) + + assertThat(isStarted).isEqualTo(false) + } + + companion object { + const val MIN_SCRIM_OFFSET = -100f + const val MAX_SCRIM_OFFSET = 0f + + const val EXPANDED_CONTENT_HEIGHT = 200f + const val COLLAPSED_CONTENT_HEIGHT = 40f + + const val MIN_VISIBLE_SCRIM_HEIGHT = 50f + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index 01e1aa59912f..c4506f2d3caa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -58,7 +58,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { } private val testScope = kosmos.testScope private val placeholderViewModel by lazy { kosmos.notificationsPlaceholderViewModel } - private val appearanceViewModel by lazy { kosmos.notificationScrollViewModel } + private val scrollViewModel by lazy { kosmos.notificationScrollViewModel } private val sceneInteractor by lazy { kosmos.sceneInteractor } private val fakeSceneDataSource by lazy { kosmos.fakeSceneDataSource } @@ -67,7 +67,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { testScope.runTest { val radius = MutableStateFlow(32) val leftOffset = MutableStateFlow(0) - val shape by collectLastValue(appearanceViewModel.shadeScrimShape(radius, leftOffset)) + val shape by collectLastValue(scrollViewModel.shadeScrimShape(radius, leftOffset)) placeholderViewModel.onScrimBoundsChanged( ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f) @@ -99,16 +99,29 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { } @Test + fun brightnessMirrorAlpha_updatesViewModel() = + testScope.runTest { + val maxAlpha by collectLastValue(scrollViewModel.maxAlpha) + assertThat(maxAlpha).isEqualTo(1f) + placeholderViewModel.setAlphaForBrightnessMirror(0.33f) + assertThat(maxAlpha).isEqualTo(0.33f) + placeholderViewModel.setAlphaForBrightnessMirror(0f) + assertThat(maxAlpha).isEqualTo(0f) + placeholderViewModel.setAlphaForBrightnessMirror(1f) + assertThat(maxAlpha).isEqualTo(1f) + } + + @Test fun shadeExpansion_goneToShade() = testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(scene = Scenes.Gone) + ObservableTransitionState.Idle(currentScene = Scenes.Gone) ) sceneInteractor.setTransitionState(transitionState) - val expandFraction by collectLastValue(appearanceViewModel.expandFraction) + val expandFraction by collectLastValue(scrollViewModel.expandFraction) assertThat(expandFraction).isEqualTo(0f) - val isScrollable by collectLastValue(appearanceViewModel.isScrollable) + val isScrollable by collectLastValue(scrollViewModel.isScrollable) assertThat(isScrollable).isFalse() fakeSceneDataSource.pause() @@ -118,6 +131,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Gone, toScene = Scenes.Shade, + currentScene = flowOf(Scenes.Shade), progress = transitionProgress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -140,12 +154,12 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(scene = Scenes.Lockscreen) + ObservableTransitionState.Idle(currentScene = Scenes.Lockscreen) ) sceneInteractor.setTransitionState(transitionState) - val expandFraction by collectLastValue(appearanceViewModel.expandFraction) + val expandFraction by collectLastValue(scrollViewModel.expandFraction) assertThat(expandFraction).isEqualTo(1f) - val isScrollable by collectLastValue(appearanceViewModel.isScrollable) + val isScrollable by collectLastValue(scrollViewModel.isScrollable) assertThat(isScrollable).isFalse() } @@ -154,14 +168,14 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { testScope.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(scene = Scenes.Shade) + ObservableTransitionState.Idle(currentScene = Scenes.Shade) ) sceneInteractor.setTransitionState(transitionState) - val expandFraction by collectLastValue(appearanceViewModel.expandFraction) + val expandFraction by collectLastValue(scrollViewModel.expandFraction) assertThat(expandFraction).isEqualTo(1f) fakeSceneDataSource.changeScene(toScene = Scenes.Shade) - val isScrollable by collectLastValue(appearanceViewModel.isScrollable) + val isScrollable by collectLastValue(scrollViewModel.isScrollable) assertThat(isScrollable).isTrue() fakeSceneDataSource.pause() @@ -172,6 +186,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = Scenes.Shade, toScene = Scenes.QuickSettings, + currentScene = flowOf(Scenes.QuickSettings), progress = transitionProgress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index c5d7e1f490ee..285326421a14 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -23,9 +23,8 @@ import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_LOW import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags -import android.platform.test.flag.junit.SetFlagsRule -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.GroupEntry @@ -44,25 +43,29 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.icon.ConversationIconManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before -import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ConversationCoordinatorTest : SysuiTestCase() { // captured listeners and pluggables: @@ -72,22 +75,20 @@ class ConversationCoordinatorTest : SysuiTestCase() { private lateinit var peopleComparator: NotifComparator private lateinit var beforeRenderListListener: OnBeforeRenderListListener + private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier + private lateinit var peopleAlertingSection: NotifSection + @Mock private lateinit var pipeline: NotifPipeline @Mock private lateinit var conversationIconManager: ConversationIconManager - @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier - @Mock private lateinit var channel: NotificationChannel @Mock private lateinit var headerController: NodeController - private lateinit var entry: NotificationEntry - private lateinit var entryA: NotificationEntry - private lateinit var entryB: NotificationEntry private lateinit var coordinator: ConversationCoordinator - @Rule @JvmField public val setFlagsRule = SetFlagsRule() - @Before fun setUp() { MockitoAnnotations.initMocks(this) + peopleNotificationIdentifier = + PeopleNotificationIdentifierImpl(mock(), GroupMembershipManagerImpl()) coordinator = ConversationCoordinator( peopleNotificationIdentifier, @@ -95,7 +96,6 @@ class ConversationCoordinatorTest : SysuiTestCase() { HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()), headerController ) - whenever(channel.isImportantConversation).thenReturn(true) coordinator.attach(pipeline) @@ -110,49 +110,65 @@ class ConversationCoordinatorTest : SysuiTestCase() { if (!SortBySectionTimeFlag.isEnabled) peopleComparator = peopleAlertingSectioner.comparator!! - entry = NotificationEntryBuilder().setChannel(channel).build() + peopleAlertingSection = NotifSection(peopleAlertingSectioner, 0) + } - val section = NotifSection(peopleAlertingSectioner, 0) - entryA = - NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("A").build() - entryB = - NotificationEntryBuilder().setChannel(channel).setSection(section).setTag("B").build() + @Test + fun priorityPeopleSectionerClaimsOnlyImportantConversations() { + val sectioner = coordinator.priorityPeopleSectioner + assertTrue(sectioner.isInSection(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON))) + assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_FULL_PERSON))) + assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_PERSON))) + assertFalse(sectioner.isInSection(makeEntryOfPeopleType(TYPE_NON_PERSON))) + assertFalse(sectioner.isInSection(NotificationEntryBuilder().build())) } @Test fun testPromotesImportantConversations() { - // only promote important conversations - assertTrue(promoter.shouldPromoteToTopLevel(entry)) + assertTrue(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON))) + assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_FULL_PERSON))) + assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_PERSON))) + assertFalse(promoter.shouldPromoteToTopLevel(makeEntryOfPeopleType(TYPE_NON_PERSON))) assertFalse(promoter.shouldPromoteToTopLevel(NotificationEntryBuilder().build())) } @Test fun testPromotedImportantConversationsMakesSummaryUnimportant() { - val altChildA = NotificationEntryBuilder().setTag("A").build() - val altChildB = NotificationEntryBuilder().setTag("B").build() - val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build() + val importantChannel = + mock<NotificationChannel>().also { + whenever(it.isImportantConversation).thenReturn(true) + } + val otherChannel = + mock<NotificationChannel>().also { + whenever(it.isImportantConversation).thenReturn(false) + } + val importantChild = + makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { setChannel(importantChannel) } + val altChildA = + makeEntryOfPeopleType(TYPE_FULL_PERSON) { setChannel(otherChannel).setTag("A") } + val altChildB = + makeEntryOfPeopleType(TYPE_FULL_PERSON) { setChannel(otherChannel).setTag("B") } + val summary = + makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { setChannel(importantChannel).setId(2) } val groupEntry = GroupEntryBuilder() .setParent(GroupEntry.ROOT_ENTRY) .setSummary(summary) - .setChildren(listOf(entry, altChildA, altChildB)) + .setChildren(listOf(importantChild, altChildA, altChildB)) .build() - assertTrue(promoter.shouldPromoteToTopLevel(entry)) + assertTrue(promoter.shouldPromoteToTopLevel(importantChild)) assertFalse(promoter.shouldPromoteToTopLevel(altChildA)) assertFalse(promoter.shouldPromoteToTopLevel(altChildB)) - NotificationEntryBuilder.setNewParent(entry, GroupEntry.ROOT_ENTRY) - GroupEntryBuilder.getRawChildren(groupEntry).remove(entry) - beforeRenderListListener.onBeforeRenderList(listOf(entry, groupEntry)) + NotificationEntryBuilder.setNewParent(importantChild, GroupEntry.ROOT_ENTRY) + GroupEntryBuilder.getRawChildren(groupEntry).remove(importantChild) + beforeRenderListListener.onBeforeRenderList(listOf(importantChild, groupEntry)) verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key))) } @Test fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() { // GIVEN - val alertingEntry = - NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_DEFAULT).build() - whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry)) - .thenReturn(TYPE_PERSON) + val alertingEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_DEFAULT) } // put alerting people notifications in this section assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue() @@ -162,10 +178,7 @@ class ConversationCoordinatorTest : SysuiTestCase() { @EnableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) fun testInAlertingPeopleSectionWhenTheImportanceIsLowerThanDefault() { // GIVEN - val silentEntry = - NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() - whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry)) - .thenReturn(TYPE_PERSON) + val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) } // THEN put silent people notifications in alerting section assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isTrue() @@ -175,10 +188,7 @@ class ConversationCoordinatorTest : SysuiTestCase() { @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() { // GIVEN - val silentEntry = - NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() - whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry)) - .thenReturn(TYPE_PERSON) + val silentEntry = makeEntryOfPeopleType(TYPE_PERSON) { setImportance(IMPORTANCE_LOW) } // THEN put silent people notifications in this section assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue() @@ -191,18 +201,14 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test fun testNotInPeopleSection() { // GIVEN - val entry = - NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_LOW).build() + val entry = makeEntryOfPeopleType(TYPE_NON_PERSON) { setImportance(IMPORTANCE_LOW) } val importantEntry = - NotificationEntryBuilder().setChannel(channel).setImportance(IMPORTANCE_HIGH).build() - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry)) - .thenReturn(TYPE_NON_PERSON) - whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry)) - .thenReturn(TYPE_NON_PERSON) + makeEntryOfPeopleType(TYPE_NON_PERSON) { setImportance(IMPORTANCE_HIGH) } // THEN - only put people notification either silent or alerting - if (!SortBySectionTimeFlag.isEnabled) + if (!SortBySectionTimeFlag.isEnabled) { assertThat(peopleSilentSectioner.isInSection(entry)).isFalse() + } assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse() } @@ -210,22 +216,16 @@ class ConversationCoordinatorTest : SysuiTestCase() { fun testInAlertingPeopleSectionWhenThereIsAnImportantChild() { // GIVEN val altChildA = - NotificationEntryBuilder().setTag("A").setImportance(IMPORTANCE_DEFAULT).build() - val altChildB = NotificationEntryBuilder().setTag("B").setImportance(IMPORTANCE_LOW).build() - val summary = - NotificationEntryBuilder() - .setId(2) - .setImportance(IMPORTANCE_LOW) - .setChannel(channel) - .build() + makeEntryOfPeopleType(TYPE_NON_PERSON) { setTag("A").setImportance(IMPORTANCE_DEFAULT) } + val altChildB = + makeEntryOfPeopleType(TYPE_NON_PERSON) { setTag("B").setImportance(IMPORTANCE_LOW) } + val summary = makeEntryOfPeopleType(TYPE_PERSON) { setId(2).setImportance(IMPORTANCE_LOW) } val groupEntry = GroupEntryBuilder() .setParent(GroupEntry.ROOT_ENTRY) .setSummary(summary) .setChildren(listOf(altChildA, altChildB)) .build() - whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary)) - .thenReturn(TYPE_PERSON) // THEN assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue() } @@ -233,10 +233,12 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) fun testComparatorPutsImportantPeopleFirst() { - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA)) - .thenReturn(TYPE_IMPORTANT_PERSON) - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB)) - .thenReturn(TYPE_PERSON) + val entryA = + makeEntryOfPeopleType(TYPE_IMPORTANT_PERSON) { + setSection(peopleAlertingSection).setTag("A") + } + val entryB = + makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("B") } // only put people notifications in this section assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(-1) @@ -245,10 +247,10 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Test @DisableFlags(Flags.FLAG_SORT_SECTION_BY_TIME) fun testComparatorEquatesPeopleWithSameType() { - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryA)) - .thenReturn(TYPE_PERSON) - whenever(peopleNotificationIdentifier.getPeopleNotificationType(entryB)) - .thenReturn(TYPE_PERSON) + val entryA = + makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("A") } + val entryB = + makeEntryOfPeopleType(TYPE_PERSON) { setSection(peopleAlertingSection).setTag("B") } // only put people notifications in this section assertThat(peopleComparator.compare(entryA, entryB)).isEqualTo(0) @@ -259,4 +261,23 @@ class ConversationCoordinatorTest : SysuiTestCase() { fun testNoSecondarySortForConversations() { assertThat(peopleAlertingSectioner.comparator).isNull() } + + private fun makeEntryOfPeopleType( + @PeopleNotificationType type: Int, + buildBlock: NotificationEntryBuilder.() -> Unit = {} + ): NotificationEntry { + val channel: NotificationChannel = mock() + whenever(channel.isImportantConversation).thenReturn(type == TYPE_IMPORTANT_PERSON) + val entry = + NotificationEntryBuilder() + .updateRanking { + it.setIsConversation(type != TYPE_NON_PERSON) + it.setShortcutInfo(if (type >= TYPE_FULL_PERSON) mock() else null) + it.setChannel(channel) + } + .also(buildBlock) + .build() + assertEquals(type, peopleNotificationIdentifier.getPeopleNotificationType(entry)) + return entry + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java index 50ae98557de6..ce134e64bf57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java @@ -31,9 +31,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.compose.animation.scene.ObservableTransitionState; @@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.kotlin.JavaAdapter; @@ -75,7 +76,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.verification.VerificationMode; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class VisualStabilityCoordinatorTest extends SysuiTestCase { @@ -86,6 +87,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock private StatusBarStateController mStatusBarStateController; @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener; + @Mock private SeenNotificationsInteractor mSeenNotificationsInteractor; @Mock private HeadsUpManager mHeadsUpManager; @Mock private VisibilityLocationProvider mVisibilityLocationProvider; @Mock private VisualStabilityProvider mVisualStabilityProvider; @@ -121,6 +123,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase { mHeadsUpManager, mShadeAnimationInteractor, mJavaAdapter, + mSeenNotificationsInteractor, mStatusBarStateController, mVisibilityLocationProvider, mVisualStabilityProvider, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt index bba9991883f5..8b4265f552fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractorTest.kt @@ -23,8 +23,14 @@ 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.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository +import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications @@ -41,9 +47,19 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @EnableFlags(NotificationsHeadsUpRefactor.FLAG_NAME) class HeadsUpNotificationInteractorTest : SysuiTestCase() { - private val kosmos = testKosmos() + private val kosmos = + testKosmos().apply { + fakeKeyguardTransitionRepository = + FakeKeyguardTransitionRepository(initInLockscreen = false) + } private val testScope = kosmos.testScope - private val repository = kosmos.headsUpNotificationRepository + private val faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } + private val headsUpRepository by lazy { kosmos.headsUpNotificationRepository } + private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } + private val keyguardViewStateRepository by lazy { + kosmos.notificationsKeyguardViewStateRepository + } + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } private val underTest = kosmos.headsUpNotificationInteractor @@ -60,7 +76,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // WHEN no pinned rows are set - repository.setNotifications( + headsUpRepository.setNotifications( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), @@ -76,7 +92,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { testScope.runTest { val hasPinnedRows by collectLastValue(underTest.hasPinnedRows) // WHEN a pinned rows is set - repository.setNotifications( + headsUpRepository.setNotifications( fakeHeadsUpRowRepository("key 0", isPinned = true), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), @@ -98,7 +114,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) - repository.setNotifications(rows) + headsUpRepository.setNotifications(rows) runCurrent() // WHEN a row gets pinned @@ -120,7 +136,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) - repository.setNotifications(rows) + headsUpRepository.setNotifications(rows) runCurrent() // THEN that row gets unpinned @@ -144,7 +160,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { testScope.runTest { val pinnedHeadsUpRows by collectLastValue(underTest.pinnedHeadsUpRows) // WHEN no rows are pinned - repository.setNotifications( + headsUpRepository.setNotifications( fakeHeadsUpRowRepository("key 0"), fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), @@ -166,7 +182,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2"), ) - repository.setNotifications(rows) + headsUpRepository.setNotifications(rows) runCurrent() // THEN the unpinned rows are filtered @@ -184,7 +200,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2"), ) - repository.setNotifications(rows) + headsUpRepository.setNotifications(rows) runCurrent() // WHEN all rows gets pinned @@ -206,7 +222,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2", isPinned = true), ) - repository.setNotifications(rows) + headsUpRepository.setNotifications(rows) runCurrent() // THEN no rows are filtered @@ -224,7 +240,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { fakeHeadsUpRowRepository("key 1", isPinned = true), fakeHeadsUpRowRepository("key 2", isPinned = true), ) - repository.setNotifications(rows) + headsUpRepository.setNotifications(rows) runCurrent() // WHEN a row gets unpinned @@ -246,7 +262,7 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { fakeHeadsUpRowRepository("key 1"), fakeHeadsUpRowRepository("key 2"), ) - repository.setNotifications(rows) + headsUpRepository.setNotifications(rows) runCurrent() rows[0].isPinned.value = true @@ -262,6 +278,96 @@ class HeadsUpNotificationInteractorTest : SysuiTestCase() { assertThat(pinnedHeadsUpRows).containsExactly(rows[0]) } + @Test + fun showHeadsUpStatusBar_true() = + testScope.runTest { + val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + + // WHEN a row is pinned + headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + + assertThat(showHeadsUpStatusBar).isTrue() + } + + @Test + fun showHeadsUpStatusBar_withoutPinnedNotifications_false() = + testScope.runTest { + val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + + // WHEN no row is pinned + headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = false)) + + assertThat(showHeadsUpStatusBar).isFalse() + } + + @Test + fun showHeadsUpStatusBar_whenShadeExpanded_false() = + testScope.runTest { + val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + + // WHEN a row is pinned + headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + // AND the shade is expanded + shadeTestUtil.setShadeExpansion(1.0f) + + assertThat(showHeadsUpStatusBar).isFalse() + } + + @Test + fun showHeadsUpStatusBar_notificationsAreHidden_false() = + testScope.runTest { + val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + + // WHEN a row is pinned + headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + // AND the notifications are hidden + keyguardViewStateRepository.areNotificationsFullyHidden.value = true + + assertThat(showHeadsUpStatusBar).isFalse() + } + + @Test + fun showHeadsUpStatusBar_onLockScreen_false() = + testScope.runTest { + val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + + // WHEN a row is pinned + headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + // AND the lock screen is shown + keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN) + + assertThat(showHeadsUpStatusBar).isFalse() + } + + @Test + fun showHeadsUpStatusBar_onByPassLockScreen_true() = + testScope.runTest { + val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + + // WHEN a row is pinned + headsUpRepository.setNotifications(fakeHeadsUpRowRepository("key 0", isPinned = true)) + // AND the lock screen is shown + keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN) + // AND bypass is enabled + faceAuthRepository.isBypassEnabled.value = true + + assertThat(showHeadsUpStatusBar).isTrue() + } + + @Test + fun showHeadsUpStatusBar_onByPassLockScreen_withoutNotifications_false() = + testScope.runTest { + val showHeadsUpStatusBar by collectLastValue(underTest.showHeadsUpStatusBar) + + // WHEN no pinned rows + // AND the lock screen is shown + keyguardTransitionRepository.emitInitialStepsFromOff(to = KeyguardState.LOCKSCREEN) + // AND bypass is enabled + faceAuthRepository.isBypassEnabled.value = true + + assertThat(showHeadsUpStatusBar).isFalse() + } + private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) = FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply { this.isPinned.value = isPinned diff --git a/packages/SystemUI/tests/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 78b76151e7e6..9367a93a2890 100644 --- a/packages/SystemUI/tests/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 @@ -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. @@ -18,116 +18,92 @@ package com.android.systemui.statusbar.notification.icon.ui.viewmodel import android.graphics.Rect import android.graphics.drawable.Icon -import androidx.test.ext.junit.runners.AndroidJUnit4 +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.flags.Flags -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.andSceneContainer +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel 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.plugins.DarkIconDispatcher -import com.android.systemui.power.data.repository.FakePowerRepository +import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.statusbar.notification.data.model.activeNotificationModel -import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore -import com.android.systemui.statusbar.notification.data.repository.HeadsUpNotificationIconViewStateRepository -import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.data.repository.headsUpNotificationIconViewStateRepository import com.android.systemui.statusbar.phone.SysuiDarkIconDispatcher -import com.android.systemui.statusbar.phone.data.repository.FakeDarkIconRepository -import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository -import com.android.systemui.user.domain.UserDomainLayerModule +import com.android.systemui.statusbar.phone.data.repository.fakeDarkIconRepository +import com.android.systemui.statusbar.phone.dozeParameters +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat -import dagger.BindsInstance -import dagger.Component +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(AndroidJUnit4::class) -class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { - - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - BiometricsDomainLayerModule::class, - UserDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<NotificationIconContainerStatusBarViewModel> { - - val activeNotificationsRepository: ActiveNotificationListRepository - val darkIconRepository: FakeDarkIconRepository - val deviceProvisioningRepository: FakeDeviceProvisioningRepository - val headsUpViewStateRepository: HeadsUpNotificationIconViewStateRepository - val keyguardTransitionRepository: FakeKeyguardTransitionRepository - val keyguardRepository: FakeKeyguardRepository - val powerRepository: FakePowerRepository - val shadeRepository: FakeShadeRepository - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - mocks: TestMocksModule, - featureFlags: FakeFeatureFlagsClassicModule, - ): TestComponent +@RunWith(ParameterizedAndroidJunit4::class) +class NotificationIconContainerStatusBarViewModelTest(flags: FlagsParameterization) : + SysuiTestCase() { + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() } } - private val dozeParams: DozeParameters = mock() - - private val testComponent: TestComponent = - DaggerNotificationIconContainerStatusBarViewModelTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { - set(Flags.FULL_SCREEN_USER_SWITCHER, value = false) - }, - mocks = - TestMocksModule( - dozeParameters = dozeParams, - ), - ) + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val powerRepository = kosmos.fakePowerRepository + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val darkIconRepository = kosmos.fakeDarkIconRepository + private val headsUpViewStateRepository = kosmos.headsUpNotificationIconViewStateRepository + private val activeNotificationsRepository = kosmos.activeNotificationListRepository + + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + private val dozeParams = kosmos.dozeParameters + + lateinit var underTest: NotificationIconContainerStatusBarViewModel @Before fun setup() { - testComponent.apply { - keyguardRepository.setKeyguardShowing(false) - powerRepository.updateWakefulness( - rawState = WakefulnessState.AWAKE, - lastWakeReason = WakeSleepReason.OTHER, - lastSleepReason = WakeSleepReason.OTHER, - ) - } + underTest = kosmos.notificationIconContainerStatusBarViewModel + keyguardRepository.setKeyguardShowing(false) + powerRepository.updateWakefulness( + rawState = WakefulnessState.AWAKE, + lastWakeReason = WakeSleepReason.OTHER, + lastSleepReason = WakeSleepReason.OTHER, + ) } @Test fun animationsEnabled_isFalse_whenDeviceAsleepAndNotPulsing() = - testComponent.runTest { + testScope.runTest { powerRepository.updateWakefulness( rawState = WakefulnessState.ASLEEP, lastWakeReason = WakeSleepReason.POWER_BUTTON, @@ -150,7 +126,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun animationsEnabled_isTrue_whenDeviceAsleepAndPulsing() = - testComponent.runTest { + testScope.runTest { powerRepository.updateWakefulness( rawState = WakefulnessState.ASLEEP, lastWakeReason = WakeSleepReason.POWER_BUTTON, @@ -173,7 +149,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun animationsEnabled_isFalse_whenStartingToSleepAndNotControlScreenOff() = - testComponent.runTest { + testScope.runTest { powerRepository.updateWakefulness( rawState = WakefulnessState.STARTING_TO_SLEEP, lastWakeReason = WakeSleepReason.POWER_BUTTON, @@ -194,7 +170,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun animationsEnabled_isTrue_whenStartingToSleepAndControlScreenOff() = - testComponent.runTest { + testScope.runTest { val animationsEnabled by collectLastValue(underTest.animationsEnabled) assertThat(animationsEnabled).isTrue() @@ -218,7 +194,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun animationsEnabled_isTrue_whenNotAsleep() = - testComponent.runTest { + testScope.runTest { powerRepository.updateWakefulness( rawState = WakefulnessState.AWAKE, lastWakeReason = WakeSleepReason.POWER_BUTTON, @@ -236,7 +212,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun animationsEnabled_isTrue_whenKeyguardIsNotShowing() = - testComponent.runTest { + testScope.runTest { val animationsEnabled by collectLastValue(underTest.animationsEnabled) keyguardTransitionRepository.sendTransitionStep( @@ -257,7 +233,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun iconColors_testsDarkBounds() = - testComponent.runTest { + testScope.runTest { darkIconRepository.darkState.value = SysuiDarkIconDispatcher.DarkChange( emptyList(), @@ -280,7 +256,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun iconColors_staticDrawableColor_notInDarkTintArea() = - testComponent.runTest { + testScope.runTest { darkIconRepository.darkState.value = SysuiDarkIconDispatcher.DarkChange( listOf(Rect(0, 0, 5, 5)), @@ -295,7 +271,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun iconColors_notInDarkTintArea() = - testComponent.runTest { + testScope.runTest { darkIconRepository.darkState.value = SysuiDarkIconDispatcher.DarkChange( listOf(Rect(0, 0, 5, 5)), @@ -309,9 +285,9 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun isolatedIcon_animateOnAppear_shadeCollapsed() = - testComponent.runTest { + testScope.runTest { val icon: Icon = mock() - shadeRepository.setLegacyShadeExpansion(0f) + shadeTestUtil.setShadeExpansion(0f) activeNotificationsRepository.activeNotifications.value = ActiveNotificationsStore.Builder() .apply { @@ -336,9 +312,9 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun isolatedIcon_dontAnimateOnAppear_shadeExpanded() = - testComponent.runTest { + testScope.runTest { val icon: Icon = mock() - shadeRepository.setLegacyShadeExpansion(.5f) + shadeTestUtil.setShadeExpansion(.5f) activeNotificationsRepository.activeNotifications.value = ActiveNotificationsStore.Builder() .apply { @@ -363,7 +339,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun isolatedIcon_updateWhenIconDataChanges() = - testComponent.runTest { + testScope.runTest { val icon: Icon = mock() val isolatedIcon by collectLastValue(underTest.isolatedIcon) runCurrent() @@ -390,7 +366,7 @@ class NotificationIconContainerStatusBarViewModelTest : SysuiTestCase() { @Test fun isolatedIcon_lastMessageIsFromReply_notNull() = - testComponent.runTest { + testScope.runTest { val icon: Icon = mock() headsUpViewStateRepository.isolatedNotification.value = "notif1" activeNotificationsRepository.activeNotifications.value = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt index 327a07d6179f..327a07d6179f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt 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..9fde116e968c 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 @@ -506,9 +524,9 @@ class NotificationListViewModelTest : SysuiTestCase() { // WHEN there are no pinned rows val rows = arrayListOf( - fakeHeadsUpRowRepository(key = "0"), - fakeHeadsUpRowRepository(key = "1"), - fakeHeadsUpRowRepository(key = "2"), + FakeHeadsUpRowRepository(key = "0"), + FakeHeadsUpRowRepository(key = "1"), + FakeHeadsUpRowRepository(key = "2"), ) headsUpRepository.setNotifications( rows, @@ -547,8 +565,8 @@ class NotificationListViewModelTest : SysuiTestCase() { val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow) headsUpRepository.setNotifications( - fakeHeadsUpRowRepository(key = "0", isPinned = true), - fakeHeadsUpRowRepository(key = "1") + FakeHeadsUpRowRepository(key = "0", isPinned = true), + FakeHeadsUpRowRepository(key = "1") ) runCurrent() @@ -562,8 +580,8 @@ class NotificationListViewModelTest : SysuiTestCase() { val hasPinnedHeadsUpRow by collectLastValue(underTest.hasPinnedHeadsUpRow) headsUpRepository.setNotifications( - fakeHeadsUpRowRepository(key = "0"), - fakeHeadsUpRowRepository(key = "1"), + FakeHeadsUpRowRepository(key = "0"), + FakeHeadsUpRowRepository(key = "1"), ) runCurrent() @@ -588,8 +606,8 @@ class NotificationListViewModelTest : SysuiTestCase() { testScope.runTest { val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled) - fakeShadeRepository.setQsExpansion(0.0f) - fakeKeyguardRepository.setKeyguardShowing(false) + shadeTestUtil.setQsExpansion(0.0f) + fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) runCurrent() assertThat(animationsEnabled).isTrue() @@ -601,15 +619,10 @@ class NotificationListViewModelTest : SysuiTestCase() { testScope.runTest { val animationsEnabled by collectLastValue(underTest.headsUpAnimationsEnabled) - fakeShadeRepository.setQsExpansion(0.0f) - fakeKeyguardRepository.setKeyguardShowing(true) + shadeTestUtil.setQsExpansion(0.0f) + fakeKeyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) runCurrent() assertThat(animationsEnabled).isFalse() } - - private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) = - FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply { - this.isPinned.value = isPinned - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt index 1f0812da9fe9..ee9fd3494d96 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt @@ -44,13 +44,4 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() { collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds) assertThat(stackBounds).isEqualTo(bounds) } - - @Test - fun onStackBoundsChanged() = - kosmos.testScope.runTest { - underTest.onStackBoundsChanged(top = 5f, bottom = 500f) - assertThat(kosmos.notificationStackAppearanceInteractor.stackTop.value).isEqualTo(5f) - assertThat(kosmos.notificationStackAppearanceInteractor.stackBottom.value) - .isEqualTo(500f) - } } 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 3408e06fe713..82e2bb719818 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 @@ -30,6 +30,7 @@ import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags @@ -61,6 +62,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -74,7 +76,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 @@ -88,7 +90,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } val aodBurnInViewModel = mock(AodBurnInViewModel::class.java) @@ -106,18 +108,25 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : val testScope = kosmos.testScope val configurationRepository get() = kosmos.fakeConfigurationRepository + val keyguardRepository get() = kosmos.fakeKeyguardRepository + val keyguardInteractor get() = kosmos.keyguardInteractor + val keyguardRootViewModel get() = kosmos.keyguardRootViewModel + val keyguardTransitionRepository get() = kosmos.fakeKeyguardTransitionRepository + val shadeTestUtil get() = kosmos.shadeTestUtil + val sharedNotificationContainerInteractor get() = kosmos.sharedNotificationContainerInteractor + val largeScreenHeaderHelper get() = kosmos.mockLargeScreenHeaderHelper @@ -654,26 +663,25 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : var notificationCount = 10 val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount } val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) - + advanceTimeBy(50L) showLockscreen() overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) - ) assertThat(maxNotifications).isEqualTo(10) // Also updates when directly requested (as it would from NotificationStackScrollLayout) notificationCount = 25 sharedNotificationContainerInteractor.notificationStackChanged() + advanceTimeBy(50L) assertThat(maxNotifications).isEqualTo(25) // Also ensure another collection starts with the same value. As an example, folding // then unfolding will restart the coroutine and it must get the last value immediately. val newMaxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) + advanceTimeBy(50L) assertThat(newMaxNotifications).isEqualTo(25) } @@ -683,14 +691,11 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : var notificationCount = 10 val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount } val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) - + advanceTimeBy(50L) showLockscreen() overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) - ) assertThat(maxNotifications).isEqualTo(10) @@ -718,15 +723,13 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : testScope.runTest { val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 } val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace)) + advanceTimeBy(50L) // Show lockscreen with shade expanded showLockscreenWithShadeExpanded() overrideResource(R.bool.config_use_split_notification_shade, false) configurationRepository.onAnyConfigurationChange() - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = 1f, bottom = 2f) - ) // -1 means No Limit assertThat(maxNotifications).isEqualTo(-1) @@ -819,6 +822,7 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization?) : } @Test + @BrokenWithSceneContainer(330311871) fun alphaDoesNotUpdateWhileGoneTransitionIsRunning() = testScope.runTest { val viewState = ViewStateAccessor() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt index 1501d9c3505a..b643968c6322 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ActivityStarterImplTest.kt @@ -30,7 +30,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock -import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt index 1cd12f043a72..7bc6948edf31 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostCoroutinesTest.kt @@ -35,6 +35,7 @@ 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 import org.mockito.Mockito.verify @@ -46,30 +47,32 @@ class DozeServiceHostCoroutinesTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val sceneContainerRepository = kosmos.sceneContainerRepository - private val keyguardInteractor = kosmos.keyguardInteractor + lateinit var underTest: DozeServiceHost - val underTest = - kosmos.dozeServiceHost.apply { - initialize( - /* centralSurfaces = */ mock(), - /* statusBarKeyguardViewManager = */ mock(), - /* notificationShadeWindowViewController = */ mock(), - /* ambientIndicationContainer = */ mock(), - ) - } + @Before + fun setup() { + underTest = + kosmos.dozeServiceHost.apply { + initialize( + /* centralSurfaces = */ mock(), + /* statusBarKeyguardViewManager = */ mock(), + /* notificationShadeWindowViewController = */ mock(), + /* ambientIndicationContainer = */ mock(), + ) + } + } @Test @EnableSceneContainer fun startStopDozing() = testScope.runTest { - val isDozing by collectLastValue(keyguardInteractor.isDozing) + val isDozing by collectLastValue(kosmos.keyguardInteractor.isDozing) // GIVEN a callback is set val callback: DozeHost.Callback = mock() underTest.addCallback(callback) // AND we are on the lock screen - sceneContainerRepository.changeScene(Scenes.Lockscreen) + kosmos.sceneContainerRepository.changeScene(Scenes.Lockscreen) // AND dozing is not requested yet assertThat(underTest.dozingRequested).isFalse() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt index b443489f98e2..0ca620751ddf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt @@ -259,7 +259,7 @@ class LegacyActivityStarterInternalImplTest : SysuiTestCase() { underTest.dismissKeyguardThenExecute({ true }, {}, false) verify(biometricUnlockController) - .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) + .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, null) } @Test 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/util/kotlin/BooleanFlowOperatorsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt index 03a39f8f07d8..2d8cd930d8fa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/kotlin/BooleanFlowOperatorsTest.kt @@ -23,9 +23,9 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos -import com.android.systemui.util.kotlin.BooleanFlowOperators.and +import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not -import com.android.systemui.util.kotlin.BooleanFlowOperators.or import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -45,21 +45,21 @@ class BooleanFlowOperatorsTest : SysuiTestCase() { @Test fun and_allTrue_returnsTrue() = testScope.runTest { - val result by collectLastValue(and(TRUE, TRUE)) + val result by collectLastValue(allOf(TRUE, TRUE)) assertThat(result).isTrue() } @Test fun and_anyFalse_returnsFalse() = testScope.runTest { - val result by collectLastValue(and(TRUE, FALSE, TRUE)) + val result by collectLastValue(allOf(TRUE, FALSE, TRUE)) assertThat(result).isFalse() } @Test fun and_allFalse_returnsFalse() = testScope.runTest { - val result by collectLastValue(and(FALSE, FALSE, FALSE)) + val result by collectLastValue(allOf(FALSE, FALSE, FALSE)) assertThat(result).isFalse() } @@ -68,7 +68,7 @@ class BooleanFlowOperatorsTest : SysuiTestCase() { testScope.runTest { val flow1 = MutableStateFlow(false) val flow2 = MutableStateFlow(false) - val values by collectValues(and(flow1, flow2)) + val values by collectValues(allOf(flow1, flow2)) assertThat(values).containsExactly(false) flow1.value = true @@ -81,21 +81,21 @@ class BooleanFlowOperatorsTest : SysuiTestCase() { @Test fun or_allTrue_returnsTrue() = testScope.runTest { - val result by collectLastValue(or(TRUE, TRUE)) + val result by collectLastValue(anyOf(TRUE, TRUE)) assertThat(result).isTrue() } @Test fun or_anyTrue_returnsTrue() = testScope.runTest { - val result by collectLastValue(or(FALSE, TRUE, FALSE)) + val result by collectLastValue(anyOf(FALSE, TRUE, FALSE)) assertThat(result).isTrue() } @Test fun or_allFalse_returnsFalse() = testScope.runTest { - val result by collectLastValue(or(FALSE, FALSE, FALSE)) + val result by collectLastValue(anyOf(FALSE, FALSE, FALSE)) assertThat(result).isFalse() } @@ -104,7 +104,7 @@ class BooleanFlowOperatorsTest : SysuiTestCase() { testScope.runTest { val flow1 = MutableStateFlow(false) val flow2 = MutableStateFlow(false) - val values by collectValues(or(flow1, flow2)) + val values by collectValues(anyOf(flow1, flow2)) assertThat(values).containsExactly(false) flow1.value = true diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt index 632196ccf66d..2af2602c6f52 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt @@ -21,6 +21,7 @@ import android.graphics.drawable.TestStubDrawable import android.media.AudioDeviceInfo import android.media.AudioDevicePort import android.media.AudioManager +import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.R @@ -54,6 +55,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @RunWith(AndroidJUnit4::class) @SmallTest +@TestableLooper.RunWithLooper(setAsMainLooper = true) class AudioOutputInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt index 675136c7cf26..a163ca08691e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt @@ -36,7 +36,6 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -47,19 +46,8 @@ class AudioVolumeInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() - private lateinit var underTest: AudioVolumeInteractor - - @Before - fun setup() { - with(kosmos) { - underTest = AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) - - audioRepository.setRingerMode(RingerMode(AudioManager.RINGER_MODE_NORMAL)) - - notificationsSoundPolicyRepository.updateNotificationPolicy() - notificationsSoundPolicyRepository.updateZenMode(ZenMode(Settings.Global.ZEN_MODE_OFF)) - } - } + private val underTest: AudioVolumeInteractor = + with(kosmos) { AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) } @Test fun setMuted_mutesStream() { @@ -236,6 +224,55 @@ class AudioVolumeInteractorTest : SysuiTestCase() { } } + @Test + fun testReducingVolumeToMin_mutes() = + with(kosmos) { + testScope.runTest { + val audioStreamModel by + collectLastValue(audioRepository.getAudioStream(audioStream)) + runCurrent() + + underTest.setVolume(audioStream, audioStreamModel!!.minVolume) + runCurrent() + + assertThat(audioStreamModel!!.isMuted).isTrue() + } + } + + @Test + fun testIncreasingVolumeFromMin_unmutes() = + with(kosmos) { + testScope.runTest { + val audioStreamModel by + collectLastValue(audioRepository.getAudioStream(audioStream)) + audioRepository.setMuted(audioStream, true) + audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume) + runCurrent() + + underTest.setVolume(audioStream, audioStreamModel!!.maxVolume) + runCurrent() + + assertThat(audioStreamModel!!.isMuted).isFalse() + } + } + + @Test + fun testUnmutingMinVolume_increasesVolume() = + with(kosmos) { + testScope.runTest { + val audioStreamModel by + collectLastValue(audioRepository.getAudioStream(audioStream)) + audioRepository.setMuted(audioStream, true) + audioRepository.setVolume(audioStream, audioStreamModel!!.minVolume) + runCurrent() + + underTest.setMuted(audioStream, false) + runCurrent() + + assertThat(audioStreamModel!!.volume).isGreaterThan(audioStreamModel!!.minVolume) + } + } + private companion object { val audioStream = AudioStream(AudioManager.STREAM_SYSTEM) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt index dc9613904e4e..9a952742f735 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepositoryTest.kt @@ -18,20 +18,13 @@ package com.android.systemui.volume.panel.component.anc.data.repository import android.bluetooth.BluetoothDevice import android.net.Uri +import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.settingslib.bluetooth.CachedBluetoothDevice -import com.android.settingslib.media.BluetoothMediaDevice -import com.android.settingslib.media.MediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope 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.volume.localMediaRepository -import com.android.systemui.volume.localMediaRepositoryFactory import com.android.systemui.volume.panel.component.anc.FakeSliceFactory import com.android.systemui.volume.panel.component.anc.sliceViewManager import com.google.common.truth.Truth.assertThat @@ -41,10 +34,14 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.kotlin.any +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) class AncSliceRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -57,22 +54,23 @@ class AncSliceRepositoryTest : SysuiTestCase() { val slice = FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true) whenever(sliceViewManager.bindSlice(any<Uri>())).thenReturn(slice) - underTest = - AncSliceRepositoryImpl( - localMediaRepositoryFactory, - testScope.testScheduler, - sliceViewManager, - ) + underTest = AncSliceRepositoryImpl(testScope.testScheduler, sliceViewManager) } } @Test - fun noConnectedDevice_noSlice() { + fun connectedDevice_noUri_noSlice() { with(kosmos) { testScope.runTest { - localMediaRepository.updateCurrentConnectedDevice(null) - - val slice by collectLastValue(underTest.ancSlice(1, false, false)) + val slice by + collectLastValue( + underTest.ancSlice( + device = createMediaDevice(""), + width = 1, + isCollapsed = false, + hideLabel = false, + ) + ) runCurrent() assertThat(slice).isNull() @@ -81,12 +79,18 @@ class AncSliceRepositoryTest : SysuiTestCase() { } @Test - fun connectedDevice_sliceReturned() { + fun connectedDevice_hasUri_sliceReturned() { with(kosmos) { testScope.runTest { - localMediaRepository.updateCurrentConnectedDevice(createMediaDevice()) - - val slice by collectLastValue(underTest.ancSlice(1, false, false)) + val slice by + collectLastValue( + underTest.ancSlice( + device = createMediaDevice("content://test.slice"), + width = 1, + isCollapsed = false, + hideLabel = false, + ) + ) runCurrent() assertThat(slice).isNotNull() @@ -94,21 +98,13 @@ class AncSliceRepositoryTest : SysuiTestCase() { } } - private fun createMediaDevice(sliceUri: String = "content://test.slice"): MediaDevice { - val bluetoothDevice: BluetoothDevice = mock { - whenever(getMetadata(any())) - .thenReturn( - ("<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + - sliceUri + - "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>") - .toByteArray() - ) - } - val cachedBluetoothDevice: CachedBluetoothDevice = mock { - whenever(device).thenReturn(bluetoothDevice) - } - return mock<BluetoothMediaDevice> { - whenever(cachedDevice).thenReturn(cachedBluetoothDevice) - } + private fun createMediaDevice(sliceUri: String): BluetoothDevice = mock { + on { getMetadata(any()) } + .thenReturn( + ("<HEARABLE_CONTROL_SLICE_WITH_WIDTH>" + + sliceUri + + "</HEARABLE_CONTROL_SLICE_WITH_WIDTH>") + .toByteArray() + ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt index 553aed8cfb05..8d052fe12a5a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/AncAvailabilityCriteriaTest.kt @@ -16,7 +16,9 @@ package com.android.systemui.volume.panel.component.anc.domain +import android.media.AudioManager import android.net.Uri +import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -26,10 +28,13 @@ 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.volume.data.repository.audioRepository +import com.android.systemui.volume.localMediaRepository import com.android.systemui.volume.panel.component.anc.FakeSliceFactory import com.android.systemui.volume.panel.component.anc.ancSliceInteractor import com.android.systemui.volume.panel.component.anc.ancSliceRepository import com.android.systemui.volume.panel.component.anc.sliceViewManager +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.TestMediaDevicesFactory import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -41,6 +46,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) class AncAvailabilityCriteriaTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -74,6 +80,10 @@ class AncAvailabilityCriteriaTest : SysuiTestCase() { fun hasSlice_available() { with(kosmos) { testScope.runTest { + audioRepository.setMode(AudioManager.MODE_NORMAL) + localMediaRepository.updateCurrentConnectedDevice( + TestMediaDevicesFactory.bluetoothMediaDevice() + ) ancSliceRepository.putSlice( 1, FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt index 81e6ac412404..741671e6a8a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractorTest.kt @@ -16,15 +16,21 @@ package com.android.systemui.volume.panel.component.anc.domain.interactor +import android.media.AudioManager +import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos +import com.android.systemui.volume.data.repository.audioRepository +import com.android.systemui.volume.localMediaRepository import com.android.systemui.volume.panel.component.anc.FakeSliceFactory +import com.android.systemui.volume.panel.component.anc.ancSliceInteractor import com.android.systemui.volume.panel.component.anc.ancSliceRepository import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.TestMediaDevicesFactory import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -36,6 +42,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) class AncSliceInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -43,14 +50,12 @@ class AncSliceInteractorTest : SysuiTestCase() { private lateinit var underTest: AncSliceInteractor @Before - fun setup() { - with(kosmos) { - underTest = AncSliceInteractor(ancSliceRepository, testScope.backgroundScope) - } + fun setUp() { + underTest = kosmos.ancSliceInteractor } @Test - fun errorSlice_returnsNull() { + fun errorSlice_returnsUnavailable() { with(kosmos) { testScope.runTest { ancSliceRepository.putSlice( @@ -67,7 +72,7 @@ class AncSliceInteractorTest : SysuiTestCase() { } @Test - fun noSliceItem_returnsNull() { + fun noSliceItem_returnsUnavailable() { with(kosmos) { testScope.runTest { ancSliceRepository.putSlice( @@ -84,9 +89,31 @@ class AncSliceInteractorTest : SysuiTestCase() { } @Test + fun sliceItem_noError_noDevice_returnsUnavailable() { + with(kosmos) { + testScope.runTest { + ancSliceRepository.putSlice( + 1, + FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true) + ) + + val slice by collectLastValue(underTest.ancSlices) + runCurrent() + + assertThat(slice).isInstanceOf(AncSlices.Unavailable::class.java) + } + } + } + + @Test fun sliceItem_noError_returnsSlice() { with(kosmos) { testScope.runTest { + audioRepository.setMode(AudioManager.MODE_NORMAL) + localMediaRepository.updateCurrentConnectedDevice( + TestMediaDevicesFactory.bluetoothMediaDevice() + ) + ancSliceRepository.putSlice( 1, FakeSliceFactory.createSlice(hasError = false, hasSliceItem = true) 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/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt index 64c9429cbe25..46df0c227f1c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt @@ -16,17 +16,16 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor -import android.os.Handler import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.volume.localMediaController import com.android.systemui.volume.mediaControllerRepository +import com.android.systemui.volume.mediaDeviceSessionInteractor import com.android.systemui.volume.mediaOutputInteractor import com.android.systemui.volume.panel.shared.model.filterData import com.android.systemui.volume.remoteMediaController @@ -55,12 +54,7 @@ class MediaDeviceSessionInteractorTest : SysuiTestCase() { listOf(localMediaController, remoteMediaController) ) - underTest = - MediaDeviceSessionInteractor( - testScope.testScheduler, - Handler(TestableLooper.get(kosmos.testCase).looper), - mediaControllerRepository, - ) + underTest = mediaDeviceSessionInteractor } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractorTest.kt new file mode 100644 index 000000000000..9e86cedb6732 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractorTest.kt @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor + +import android.media.AudioAttributes +import android.media.VolumeProvider +import android.media.session.MediaController +import android.media.session.PlaybackState +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.volume.data.repository.FakeLocalMediaRepository +import com.android.systemui.volume.localMediaController +import com.android.systemui.volume.localMediaRepositoryFactory +import com.android.systemui.volume.localPlaybackInfo +import com.android.systemui.volume.localPlaybackStateBuilder +import com.android.systemui.volume.mediaControllerRepository +import com.android.systemui.volume.mediaOutputInteractor +import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession +import com.android.systemui.volume.panel.shared.model.Result +import com.android.systemui.volume.remoteMediaController +import com.android.systemui.volume.remotePlaybackInfo +import com.android.systemui.volume.remotePlaybackStateBuilder +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(setAsMainLooper = true) +class MediaOutputInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private lateinit var underTest: MediaOutputInteractor + + @Before + fun setUp() = + with(kosmos) { + localMediaRepositoryFactory.setLocalMediaRepository( + "local.test.pkg", + FakeLocalMediaRepository().apply { + updateCurrentConnectedDevice( + mock { whenever(name).thenReturn("local_media_device") } + ) + }, + ) + localMediaRepositoryFactory.setLocalMediaRepository( + "remote.test.pkg", + FakeLocalMediaRepository().apply { + updateCurrentConnectedDevice( + mock { whenever(name).thenReturn("remote_media_device") } + ) + }, + ) + + underTest = kosmos.mediaOutputInteractor + } + + @Test + fun noActiveMediaDeviceSessions_nulls() = + with(kosmos) { + testScope.runTest { + mediaControllerRepository.setActiveSessions(emptyList()) + + val activeMediaDeviceSessions by + collectLastValue(underTest.activeMediaDeviceSessions) + runCurrent() + + assertThat(activeMediaDeviceSessions!!.local).isNull() + assertThat(activeMediaDeviceSessions!!.remote).isNull() + } + } + + @Test + fun activeMediaDeviceSessions_areParsed() = + with(kosmos) { + testScope.runTest { + mediaControllerRepository.setActiveSessions( + listOf(localMediaController, remoteMediaController) + ) + + val activeMediaDeviceSessions by + collectLastValue(underTest.activeMediaDeviceSessions) + runCurrent() + + with(activeMediaDeviceSessions!!.local!!) { + assertThat(packageName).isEqualTo("local.test.pkg") + assertThat(appLabel).isEqualTo("local_media_controller_label") + assertThat(canAdjustVolume).isTrue() + } + with(activeMediaDeviceSessions!!.remote!!) { + assertThat(packageName).isEqualTo("remote.test.pkg") + assertThat(appLabel).isEqualTo("remote_media_controller_label") + assertThat(canAdjustVolume).isTrue() + } + } + } + + @Test + fun activeMediaDeviceSessions_volumeControlFixed_cantAdjustVolume() = + with(kosmos) { + testScope.runTest { + localPlaybackInfo = + MediaController.PlaybackInfo( + MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, + VolumeProvider.VOLUME_CONTROL_FIXED, + 0, + 0, + AudioAttributes.Builder().build(), + "", + ) + remotePlaybackInfo = + MediaController.PlaybackInfo( + MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, + VolumeProvider.VOLUME_CONTROL_FIXED, + 0, + 0, + AudioAttributes.Builder().build(), + "", + ) + mediaControllerRepository.setActiveSessions( + listOf(localMediaController, remoteMediaController) + ) + + val activeMediaDeviceSessions by + collectLastValue(underTest.activeMediaDeviceSessions) + runCurrent() + + assertThat(activeMediaDeviceSessions!!.local!!.canAdjustVolume).isFalse() + assertThat(activeMediaDeviceSessions!!.remote!!.canAdjustVolume).isFalse() + } + } + + @Test + fun activeLocalAndRemoteSession_defaultSession_local() = + with(kosmos) { + testScope.runTest { + localPlaybackStateBuilder.setState(PlaybackState.STATE_PLAYING, 0, 0f) + remotePlaybackStateBuilder.setState(PlaybackState.STATE_PLAYING, 0, 0f) + mediaControllerRepository.setActiveSessions( + listOf(localMediaController, remoteMediaController) + ) + + val defaultActiveMediaSession by + collectLastValue(underTest.defaultActiveMediaSession) + val currentDevice by collectLastValue(underTest.currentConnectedDevice) + runCurrent() + + with((defaultActiveMediaSession as Result.Data<MediaDeviceSession?>).data!!) { + assertThat(packageName).isEqualTo("local.test.pkg") + assertThat(appLabel).isEqualTo("local_media_controller_label") + assertThat(canAdjustVolume).isTrue() + } + assertThat(currentDevice!!.name).isEqualTo("local_media_device") + } + } + + @Test + fun activeRemoteSession_defaultSession_remote() = + with(kosmos) { + testScope.runTest { + localPlaybackStateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 0f) + remotePlaybackStateBuilder.setState(PlaybackState.STATE_PLAYING, 0, 0f) + mediaControllerRepository.setActiveSessions( + listOf(localMediaController, remoteMediaController) + ) + + val defaultActiveMediaSession by + collectLastValue(underTest.defaultActiveMediaSession) + val currentDevice by collectLastValue(underTest.currentConnectedDevice) + runCurrent() + + with((defaultActiveMediaSession as Result.Data<MediaDeviceSession?>).data!!) { + assertThat(packageName).isEqualTo("remote.test.pkg") + assertThat(appLabel).isEqualTo("remote_media_controller_label") + assertThat(canAdjustVolume).isTrue() + } + assertThat(currentDevice!!.name).isEqualTo("remote_media_device") + } + } + + @Test + fun inactiveLocalAndRemoteSession_defaultSession_local() = + with(kosmos) { + testScope.runTest { + localPlaybackStateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 0f) + remotePlaybackStateBuilder.setState(PlaybackState.STATE_PAUSED, 0, 0f) + mediaControllerRepository.setActiveSessions( + listOf(localMediaController, remoteMediaController) + ) + + val defaultActiveMediaSession by + collectLastValue(underTest.defaultActiveMediaSession) + val currentDevice by collectLastValue(underTest.currentConnectedDevice) + runCurrent() + + with((defaultActiveMediaSession as Result.Data<MediaDeviceSession?>).data!!) { + assertThat(packageName).isEqualTo("local.test.pkg") + assertThat(appLabel).isEqualTo("local_media_controller_label") + assertThat(canAdjustVolume).isTrue() + } + assertThat(currentDevice!!.name).isEqualTo("local_media_device") + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt index 737b7f3e0af0..777240c57c2e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt @@ -19,13 +19,13 @@ package com.android.systemui.volume.panel.component.spatial import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.media.spatializerInteractor -import com.android.systemui.volume.mediaOutputInteractor +import com.android.systemui.volume.domain.interactor.audioOutputInteractor import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor val Kosmos.spatialAudioComponentInteractor by Kosmos.Fixture { SpatialAudioComponentInteractor( - mediaOutputInteractor, + audioOutputInteractor, spatializerInteractor, testScope.backgroundScope ) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt index e36ae60ebe7d..c6c46faf97f7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt @@ -29,7 +29,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testScope -import com.android.systemui.media.spatializerInteractor import com.android.systemui.media.spatializerRepository import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock @@ -37,9 +36,9 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.volume.localMediaController import com.android.systemui.volume.localMediaRepository import com.android.systemui.volume.mediaControllerRepository -import com.android.systemui.volume.mediaOutputInteractor import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel +import com.android.systemui.volume.panel.component.spatial.spatialAudioComponentInteractor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent @@ -76,12 +75,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { mediaControllerRepository.setActiveSessions(listOf(localMediaController)) - underTest = - SpatialAudioComponentInteractor( - mediaOutputInteractor, - spatializerInteractor, - testScope.backgroundScope, - ) + underTest = spatialAudioComponentInteractor } } 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/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt index 55e46dc1c434..e1be6b008903 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt @@ -24,16 +24,17 @@ import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel import com.android.systemui.communal.util.fakeCommunalColors import com.android.systemui.concurrency.fakeExecutor -import com.android.systemui.dock.DockManager -import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.ScreenLifecycle import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.wakefulnessLifecycle import com.android.systemui.kosmos.testScope import com.android.systemui.model.SysUiState @@ -63,7 +64,6 @@ import com.android.wm.shell.sysui.ShellInterface import java.util.Optional import java.util.concurrent.Executor import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -186,29 +186,18 @@ class WMShellTest : SysuiTestCase() { verify(mRecentTasks).setTransitionBackgroundColor(null) verify(mRecentTasks, never()).setTransitionBackgroundColor(black) - setDocked(true) - // Make communal available - kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false) - kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) - kosmos.fakeKeyguardRepository.setKeyguardShowing(true) - + // Transition to occluded from the glanceable hub. + kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.GLANCEABLE_HUB, + to = KeyguardState.OCCLUDED, + testScope + ) + kosmos.setCommunalAvailable(true) runCurrent() verify(mRecentTasks).setTransitionBackgroundColor(black) } - private fun TestScope.setDocked(docked: Boolean) { - kosmos.fakeDockManager.setIsDocked(docked) - val event = - if (docked) { - DockManager.STATE_DOCKED - } else { - DockManager.STATE_NONE - } - kosmos.fakeDockManager.setDockEvent(event) - runCurrent() - } - private companion object { val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) } 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/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java index c9e298934acc..d13c75082790 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java @@ -21,11 +21,11 @@ import android.graphics.drawable.Drawable; import android.metrics.LogMaker; import android.service.quicksettings.Tile; import android.text.TextUtils; -import android.view.View; import androidx.annotation.Nullable; import com.android.internal.logging.InstanceId; +import com.android.systemui.animation.Expandable; import com.android.systemui.plugins.annotations.DependsOn; import com.android.systemui.plugins.annotations.ProvidesInterface; import com.android.systemui.plugins.qs.QSTile.Callback; @@ -58,23 +58,23 @@ public interface QSTile { /** * The tile was clicked. * - * @param view The view that was clicked. + * @param expandable {@link Expandable} that was clicked. */ - void click(@Nullable View view); + void click(@Nullable Expandable expandable); /** * The tile secondary click was triggered. * - * @param view The view that was clicked. + * @param expandable {@link Expandable} that was clicked. */ - void secondaryClick(@Nullable View view); + void secondaryClick(@Nullable Expandable expandable); /** * The tile was long clicked. * - * @param view The view that was clicked. + * @param expandable {@link Expandable} that was clicked. */ - void longClick(@Nullable View view); + void longClick(@Nullable Expandable expandable); void userSwitch(int currentUser); diff --git a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml index a751f58344a9..370677ac0890 100644 --- a/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml +++ b/packages/SystemUI/res-keyguard/drawable/status_bar_user_chip_bg.xml @@ -16,5 +16,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/material_dynamic_neutral20" /> - <corners android:radius="@dimen/ongoing_call_chip_corner_radius" /> + <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" /> </shape> diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml index b62e684b0105..b35259acbb96 100644 --- a/packages/SystemUI/res-keyguard/values-am/strings.xml +++ b/packages/SystemUI/res-keyguard/values-am/strings.xml @@ -23,7 +23,7 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"የእርስዎን ፒን ያስገቡ"</string> <string name="keyguard_enter_pin" msgid="8114529922480276834">"ፒን ያስገቡ"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"ሥርዓተ-ጥለትዎን ያስገቡ"</string> - <string name="keyguard_enter_pattern" msgid="7616595160901084119">"ስርዓተ ጥለት ይሳሉ"</string> + <string name="keyguard_enter_pattern" msgid="7616595160901084119">"ሥርዓተ ጥለት ይሳሉ"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"ይለፍ ቃልዎን ያስገቡ"</string> <string name="keyguard_enter_password" msgid="6483623792371009758">"የይለፍ ቃል ያስገቡ"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"ልክ ያልሆነ ካርድ።"</string> diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml index 427373de8bcc..b4198806c642 100644 --- a/packages/SystemUI/res-keyguard/values-ar/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml @@ -23,9 +23,9 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"أدخل رقم التعريف الشخصي (PIN)"</string> <string name="keyguard_enter_pin" msgid="8114529922480276834">"أدخِل رقم التعريف الشخصي"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"أدخل النقش"</string> - <string name="keyguard_enter_pattern" msgid="7616595160901084119">"ارسم النقش."</string> + <string name="keyguard_enter_pattern" msgid="7616595160901084119">"ارسم النقش"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"أدخل كلمة المرور"</string> - <string name="keyguard_enter_password" msgid="6483623792371009758">"أدخِل كلمة المرور."</string> + <string name="keyguard_enter_password" msgid="6483623792371009758">"أدخِل كلمة المرور"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"بطاقة غير صالحة."</string> <string name="keyguard_charged" msgid="5478247181205188995">"اكتمل الشحن"</string> <string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • جارٍ الشحن لاسلكيًا"</string> @@ -57,11 +57,11 @@ <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> - <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"يُرجى إعادة المحاولة أو رسم النقش."</string> + <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"يُرجى إعادة المحاولة أو إدخال رقم التعريف الشخصي"</string> + <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"يُرجى إعادة المحاولة أو إدخال كلمة المرور"</string> + <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"يُرجى إعادة المحاولة أو رسم النقش"</string> <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"يجب إدخال رقم PIN لأنّك أجريت محاولات كثيرة جدًا."</string> <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"يجب إدخال كلمة المرور لأنك أجريت محاولات كثيرة جدًا."</string> <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"يجب رسم النقش لأنّك أجريت محاولات كثيرة جدًا."</string> diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml index ee27e83c587d..74b1fd3e45ba 100644 --- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"Pogrešan PIN"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Pogrešan PIN. Probajte ponovo."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Ili otključajte otiskom prsta"</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Otisak prsta neprepoznat"</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Otisak prsta nije prepoznat"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Lice nije prepoznato"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Probajte ponovo ili unesite PIN"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Probajte ponovo ili unesite lozinku"</string> diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml index 5b804e2d8532..fe6636bfa489 100644 --- a/packages/SystemUI/res-keyguard/values-bn/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml @@ -23,7 +23,7 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"পিন লিখুন"</string> <string name="keyguard_enter_pin" msgid="8114529922480276834">"পিন লিখুন"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"প্যাটার্ন আঁকুন"</string> - <string name="keyguard_enter_pattern" msgid="7616595160901084119">"প্যাটার্ন দিন"</string> + <string name="keyguard_enter_pattern" msgid="7616595160901084119">"প্যাটার্ন আঁকুন"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"পাসওয়ার্ড লিখুন"</string> <string name="keyguard_enter_password" msgid="6483623792371009758">"পাসওয়ার্ড লিখুন"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"ভুল কার্ড।"</string> diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml index 4a5e789cab8a..baef51ae9d48 100644 --- a/packages/SystemUI/res-keyguard/values-bs/strings.xml +++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml @@ -23,7 +23,7 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Unesite PIN"</string> <string name="keyguard_enter_pin" msgid="8114529922480276834">"Unesite PIN"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Unesite uzorak"</string> - <string name="keyguard_enter_pattern" msgid="7616595160901084119">"Unesite uzorak"</string> + <string name="keyguard_enter_pattern" msgid="7616595160901084119">"Nacrtajte uzorak"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"Unesite lozinku"</string> <string name="keyguard_enter_password" msgid="6483623792371009758">"Unesite lozinku"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Nevažeća kartica."</string> @@ -126,8 +126,8 @@ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Otključajte uređaj da nastavite"</string> <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"Unesite PIN da kasnije instalirate ažuriranje"</string> <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"Unesite lozinku da kasnije instalirate ažuriranje"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Unesite uzorak da kasnije instalirate ažuriranje"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"Nacrtajte uzorak da kasnije instalirate ažuriranje"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"Uređaj je ažuriran. Unesite PIN da nastavite."</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"Uređaj je ažuriran. Unesite lozinku da nastavite."</string> - <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Uređaj je ažuriran. Unesite uzorak da nastavite."</string> + <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"Uređaj je ažuriran. Nacrtajte uzorak da nastavite."</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml index 188a76a12f9f..822e16eeb79e 100644 --- a/packages/SystemUI/res-keyguard/values-fr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml @@ -23,7 +23,7 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"Saisissez votre code"</string> <string name="keyguard_enter_pin" msgid="8114529922480276834">"Saisissez le code"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Tracez votre schéma"</string> - <string name="keyguard_enter_pattern" msgid="7616595160901084119">"Dessinez un schéma"</string> + <string name="keyguard_enter_pattern" msgid="7616595160901084119">"Dessinez le schéma"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"Saisissez votre mot de passe"</string> <string name="keyguard_enter_password" msgid="6483623792371009758">"Saisissez le mot de passe"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Carte non valide."</string> diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml index e5cd788200a9..26a4dc5b8068 100644 --- a/packages/SystemUI/res-keyguard/values-gl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml @@ -57,11 +57,11 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"PIN incorrecto"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"PIN incorrecto. Téntao."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Tamén podes desbloquealo coa impresión dixital"</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Impr. dixital non recoñec."</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Impresión dixital non recoñecida"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"A cara non se recoñeceu"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Téntao de novo ou pon o PIN"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Téntao de novo ou pon o contrasinal"</string> - <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Téntao de novo ou debuxa o contrasinal"</string> + <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Téntao de novo ou debuxa o padrón"</string> <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"Requírese o PIN tras realizar demasiados intentos"</string> <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"Requírese o contrasinal tras demasiados intentos"</string> <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"Requírese o padrón tras realizar demasiados intentos"</string> diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml index 2b019039a01f..d9055817a764 100644 --- a/packages/SystemUI/res-keyguard/values-hi/strings.xml +++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml @@ -57,11 +57,11 @@ <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> - <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फिर से कोशिश करें या पैटर्न ड्रा करें"</string> + <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फिर से कोशिश करें या पैटर्न ड्रॉ करें"</string> <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"कई बार कोशिश की जा चुकी है, इसलिए पिन डालें"</string> <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"कई बार कोशिश की जा चुकी है, इसलिए पासवर्ड डालें"</string> <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"कई बार कोशिश की जा चुकी है, इसलिए पैटर्न ड्रा करें"</string> 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 f74eb6697341..2bace4a702eb 100644 --- a/packages/SystemUI/res-keyguard/values-my/strings.xml +++ b/packages/SystemUI/res-keyguard/values-my/strings.xml @@ -23,7 +23,7 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"သင့်ပင်နံပါတ် ထည့်ပါ"</string> <string name="keyguard_enter_pin" msgid="8114529922480276834">"ပင်နံပါတ်ထည့်ပါ"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"သင့်လော့ခ်ဖွင့်ပုံစံ ထည့်ပါ"</string> - <string name="keyguard_enter_pattern" msgid="7616595160901084119">"ပုံစံဆွဲပါ"</string> + <string name="keyguard_enter_pattern" msgid="7616595160901084119">"ပုံဖော်ပါ"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"သင့်စကားဝှက် ထည့်ပါ"</string> <string name="keyguard_enter_password" msgid="6483623792371009758">"စကားဝှက် ထည့်ပါ"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"ကတ် မမှန်ကန်ပါ။"</string> @@ -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 65440e872c0d..224f1aeef05d 100644 --- a/packages/SystemUI/res-keyguard/values-ne/strings.xml +++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml @@ -23,7 +23,7 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"आफ्नो PIN हाल्नुहोस्"</string> <string name="keyguard_enter_pin" msgid="8114529922480276834">"PIN हाल्नुहोस्"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"प्याटर्न हाल्नुहोस्"</string> - <string name="keyguard_enter_pattern" msgid="7616595160901084119">"प्याटर्न कोर्नुहोस्"</string> + <string name="keyguard_enter_pattern" msgid="7616595160901084119">"प्याटर्न हाल्नुहोस्"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"पासवर्ड हाल्नुहोस्"</string> <string name="keyguard_enter_password" msgid="6483623792371009758">"पासवर्ड हाल्नुहोस्"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"अमान्य कार्ड।"</string> @@ -57,11 +57,11 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"गलत PIN"</string> <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="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">"फेरि प्रयास गर्नुहोस् वा PIN हाल्नुहोस्"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"फेरि प्रयास गर्नुहोस् वा पासवर्ड हाल्नुहोस्"</string> - <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फेरि प्रयास गर्नुहोस् वा प्याटर्न कोर्नुहोस्"</string> + <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"फेरि प्रयास गर्नुहोस् वा प्याटर्न हाल्नुहोस्"</string> <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"अत्यन्तै धेरै पटक प्रयास गरिसकेपछि PIN हाल्नु पर्ने हुन्छ"</string> <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"अत्यन्तै धेरै पटक प्रयास गरिसकेपछि पासवर्ड हाल्नु पर्ने हुन्छ"</string> <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"अत्यन्तै धेरै पटक प्रयास गरिसकेपछि प्याटर्न कोर्नु पर्ने हुन्छ"</string> @@ -126,8 +126,8 @@ <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"आफ्नो डिभाइस अनलक गरी जारी राख्नुहोस्"</string> <string name="kg_prompt_unattended_update_pin" msgid="5979434876768801873">"पछि अपडेट इन्स्टल गर्न PIN हाल्नुहोस्"</string> <string name="kg_prompt_unattended_update_password" msgid="8805664437604967210">"पछि अपडेट इन्स्टल गर्न पासवर्ड हाल्नुहोस्"</string> - <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"पछि अपडेट इन्स्टल गर्न प्याटर्न कोर्नुहोस्"</string> + <string name="kg_prompt_unattended_update_pattern" msgid="8580479377489546091">"पछि अपडेट इन्स्टल गर्न प्याटर्न हाल्नुहोस्"</string> <string name="kg_prompt_after_update_pin" msgid="7051709651908643013">"डिभाइस अपडेट गरिएको छ। जारी राख्न PIN हाल्नुहोस्।"</string> <string name="kg_prompt_after_update_password" msgid="153703052501352094">"डिभाइस अपडेट गरिएको छ। जारी राख्न पासवर्ड हाल्नुहोस्।"</string> - <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"डिभाइस अपडेट गरिएको छ। जारी राख्न प्याटर्न कोर्नुहोस्।"</string> + <string name="kg_prompt_after_update_pattern" msgid="1484084551298241992">"डिभाइस अपडेट गरिएको छ। जारी राख्न प्याटर्न हाल्नुहोस्।"</string> </resources> diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml index 9fb9e1bfe8a5..a35c3e654b3f 100644 --- a/packages/SystemUI/res-keyguard/values-nl/strings.xml +++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml @@ -25,7 +25,7 @@ <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"Voer je patroon in"</string> <string name="keyguard_enter_pattern" msgid="7616595160901084119">"Teken het patroon"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"Voer je wachtwoord in"</string> - <string name="keyguard_enter_password" msgid="6483623792371009758">"Geef het wachtwoord op"</string> + <string name="keyguard_enter_password" msgid="6483623792371009758">"Voer het wachtwoord in"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"Ongeldige kaart."</string> <string name="keyguard_charged" msgid="5478247181205188995">"Opgeladen"</string> <string name="keyguard_plugged_in_wireless" msgid="2537874724955057383">"<xliff:g id="PERCENTAGE">%s</xliff:g> • Draadloos opladen"</string> @@ -59,7 +59,7 @@ <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Of ontgrendel met vingerafdruk"</string> <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Vingerafdruk niet herkend"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Gezicht niet herkend"</string> - <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Probeer het opnieuw of geef de pincode op"</string> + <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Probeer het opnieuw of voer de pincode in"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Probeer het opnieuw of geef het wachtwoord op"</string> <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"Probeer het opnieuw of teken het patroon"</string> <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"Na te veel pogingen is de pincode vereist"</string> diff --git a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml index b29220476224..9a26f7d7b8e9 100644 --- a/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res-keyguard/values-pt-rPT/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"PIN incorreto"</string> <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"PIN errado. Tente de novo."</string> <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Ou desbloqueie com a impressão digital"</string> - <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Impr. dig. não reconhecida"</string> + <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Impressão digital não reconhecida"</string> <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Rosto não reconhecido"</string> <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Tente novamente ou introduza o PIN"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Tente novamente ou introduza a palavra-passe"</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-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml index 34c93116f180..339a82a100be 100644 --- a/packages/SystemUI/res-keyguard/values-sr/strings.xml +++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml @@ -57,7 +57,7 @@ <string name="kg_wrong_pin" msgid="4160978845968732624">"Погрешан PIN"</string> <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="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">"Пробајте поново или унесите PIN"</string> <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Пробајте поново или унесите лозинку"</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-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml index 7d74c9cb8224..196df2889944 100644 --- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml @@ -23,7 +23,7 @@ <string name="keyguard_enter_your_pin" msgid="5429932527814874032">"输入您的 PIN 码"</string> <string name="keyguard_enter_pin" msgid="8114529922480276834">"输入 PIN 码"</string> <string name="keyguard_enter_your_pattern" msgid="351503370332324745">"绘制解锁图案"</string> - <string name="keyguard_enter_pattern" msgid="7616595160901084119">"绘制图案"</string> + <string name="keyguard_enter_pattern" msgid="7616595160901084119">"绘制解锁图案"</string> <string name="keyguard_enter_your_password" msgid="7225626204122735501">"输入您的密码"</string> <string name="keyguard_enter_password" msgid="6483623792371009758">"输入密码"</string> <string name="keyguard_sim_error_message_short" msgid="633630844240494070">"SIM 卡无效。"</string> @@ -61,7 +61,7 @@ <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> + <string name="kg_bio_try_again_or_pattern" msgid="4867893307468801501">"请重试,或绘制解锁图案"</string> <string name="kg_bio_too_many_attempts_pin" msgid="5850845723433047605">"如果出错的尝试次数太多,必须输入 PIN 码才能解锁"</string> <string name="kg_bio_too_many_attempts_password" msgid="5551690347827728042">"如果出错的尝试次数太多,必须输入密码才能解锁"</string> <string name="kg_bio_too_many_attempts_pattern" msgid="736884689355181602">"如果出错的尝试次数太多,必须绘制图案才能解锁"</string> diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml index c43e394cb97a..da12dd731c23 100644 --- a/packages/SystemUI/res-keyguard/values/styles.xml +++ b/packages/SystemUI/res-keyguard/values/styles.xml @@ -36,7 +36,6 @@ </style> <style name="Keyguard.Bouncer.SecondaryMessage" parent="Theme.SystemUI"> <item name="android:textSize">14sp</item> - <item name="android:lineHeight">20dp</item> <item name="android:maxLines">@integer/bouncer_secondary_message_lines</item> <item name="android:lines">@integer/bouncer_secondary_message_lines</item> <item name="android:textAlignment">center</item> diff --git a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml b/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml deleted file mode 100644 index 4181220ed68c..000000000000 --- a/packages/SystemUI/res/drawable/contrast_dialog_button_background.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* -* Copyright 2023, The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ ---> -<selector - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - - <item android:state_selected="true"> - <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurfaceHighlight" /> - <stroke - android:color="?androidprv:attr/colorAccentPrimary" - android:width="@dimen/contrast_dialog_button_stroke_width" /> - <corners android:radius="@dimen/contrast_dialog_button_radius"/> - </shape> - </item> - - <item> - <layer-list> - <item android:top="@dimen/contrast_dialog_button_stroke_width" - android:bottom="@dimen/contrast_dialog_button_stroke_width" - android:left="@dimen/contrast_dialog_button_stroke_width" - android:right="@dimen/contrast_dialog_button_stroke_width"> - <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurfaceHighlight" /> - <corners android:radius="@dimen/contrast_dialog_button_radius"/> - </shape> - </item> - </layer-list> - </item> -</selector> diff --git a/packages/SystemUI/res/drawable/hub_handle.xml b/packages/SystemUI/res/drawable/hub_handle.xml new file mode 100644 index 000000000000..8bc276ffed9e --- /dev/null +++ b/packages/SystemUI/res/drawable/hub_handle.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <corners android:radius="4dp" /> + <solid android:color="#FFFFFF" /> +</shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/ic_contrast_high.xml b/packages/SystemUI/res/drawable/ic_contrast_high.xml deleted file mode 100644 index aa5b5abc33aa..000000000000 --- a/packages/SystemUI/res/drawable/ic_contrast_high.xml +++ /dev/null @@ -1,25 +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. - --> -<vector android:autoMirrored="true" android:height="20dp" - android:viewportHeight="20" android:viewportWidth="66" - android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#F2F1E8" - android:pathData="M0.5,8C0.5,3.858 3.858,0.5 8,0.5H58C62.142,0.5 65.5,3.858 65.5,8V12C65.5,16.142 62.142,19.5 58,19.5H8C3.858,19.5 0.5,16.142 0.5,12V8Z" - android:strokeColor="#1B1C17" android:strokeWidth="1"/> - <path android:fillColor="#1B1C17" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/> - <path android:fillColor="#1B1C17" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/> - <path android:fillColor="#1B1C17" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_contrast_medium.xml b/packages/SystemUI/res/drawable/ic_contrast_medium.xml deleted file mode 100644 index 89519b86b974..000000000000 --- a/packages/SystemUI/res/drawable/ic_contrast_medium.xml +++ /dev/null @@ -1,23 +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. - --> -<vector android:autoMirrored="true" android:height="20dp" - android:viewportHeight="20" android:viewportWidth="66" - android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#F2F1E8" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/> - <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/> - <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/> - <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ic_contrast_standard.xml b/packages/SystemUI/res/drawable/ic_contrast_standard.xml deleted file mode 100644 index f914975823da..000000000000 --- a/packages/SystemUI/res/drawable/ic_contrast_standard.xml +++ /dev/null @@ -1,23 +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. - --> -<vector android:autoMirrored="true" android:height="20dp" - android:viewportHeight="20" android:viewportWidth="66" - android:width="66dp" xmlns:android="http://schemas.android.com/apk/res/android"> - <path android:fillColor="#C7C8B7" android:pathData="M0,8C0,3.582 3.582,0 8,0H58C62.418,0 66,3.582 66,8V12C66,16.418 62.418,20 58,20H8C3.582,20 0,16.418 0,12V8Z"/> - <path android:fillColor="#919283" android:pathData="M11,10m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/> - <path android:fillColor="#919283" android:pathData="M23,5L43,5A2,2 0,0 1,45 7L45,7A2,2 0,0 1,43 9L23,9A2,2 0,0 1,21 7L21,7A2,2 0,0 1,23 5z"/> - <path android:fillColor="#919283" android:pathData="M23,11L55,11A2,2 0,0 1,57 13L57,13A2,2 0,0 1,55 15L23,15A2,2 0,0 1,21 13L21,13A2,2 0,0 1,23 11z"/> -</vector> diff --git a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml index bdd6270bb50b..b9a4cbfc683e 100644 --- a/packages/SystemUI/res/drawable/ongoing_call_chip_bg.xml +++ b/packages/SystemUI/res/drawable/ongoing_activity_chip_bg.xml @@ -16,5 +16,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="?android:attr/colorAccent" /> - <corners android:radius="@dimen/ongoing_call_chip_corner_radius" /> + <corners android:radius="@dimen/ongoing_activity_chip_corner_radius" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml index cf7a730dc379..c32acf2fdea2 100644 --- a/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml +++ b/packages/SystemUI/res/drawable/qs_tile_background_flagged.xml @@ -13,14 +13,12 @@ ~ 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 - 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 --> +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="@color/qs_tile_ripple_color"> <item - android:id="@id/background"> + android:id="@android:id/mask" + android:drawable="@drawable/qs_tile_background_shape" /> + <item android:id="@id/background"> <layer-list> <item android:id="@+id/qs_tile_background_base" @@ -28,24 +26,10 @@ <item android:id="@+id/qs_tile_background_overlay"> <selector> <item - android:state_hovered="true" - android:drawable="@drawable/qs_tile_background_shape" /> - </selector> - </item> - <!-- In the layer below we have negative insets because we need the focus outline - to draw outside the bounds, around the main background. We use 5dp because - the outline stroke is 3dp and the required padding is 2dp.--> - <item - android:top="-5dp" - android:right="-5dp" - android:left="-5dp" - android:bottom="-5dp"> - <selector> - <item - android:state_focused="true" - android:drawable="@drawable/qs_tile_focused_background"/> + android:drawable="@drawable/qs_tile_background_shape" + android:state_hovered="true" /> </selector> </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/drawable/qs_tile_focused_background.xml b/packages/SystemUI/res/drawable/qs_tile_focused_background.xml index fd456df2c9d8..33f0d02efb2a 100644 --- a/packages/SystemUI/res/drawable/qs_tile_focused_background.xml +++ b/packages/SystemUI/res/drawable/qs_tile_focused_background.xml @@ -13,10 +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" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <corners android:radius="30dp"/> - <stroke android:width="3dp" android:color="?androidprv:attr/materialColorSecondaryFixed"/> -</shape>
\ No newline at end of file + +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:inset="-5dp"> + <shape xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <corners android:radius="30dp" /> + <stroke + android:width="3dp" + android:color="?androidprv:attr/materialColorSecondaryFixed" /> + </shape> +</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml index bb8cece9203b..ad6c154692ec 100644 --- a/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml +++ b/packages/SystemUI/res/drawable/shelf_action_chip_container_background.xml @@ -14,10 +14,14 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<shape +<inset xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <solid android:color="?androidprv:attr/materialColorSurfaceBright"/> - <corners android:radius="10000dp"/> <!-- fully-rounded radius --> -</shape> + android:insetLeft="@dimen/overlay_action_container_minimum_edge_spacing" + android:insetRight="@dimen/overlay_action_container_minimum_edge_spacing"> + <shape + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:shape="rectangle"> + <solid android:color="?androidprv:attr/materialColorSurfaceBright"/> + <corners android:radius="10000dp"/> <!-- fully-rounded radius --> + </shape> +</inset>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml index a5b44e564157..0a1f2a8f5048 100644 --- a/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml +++ b/packages/SystemUI/res/drawable/shelf_action_chip_divider.xml @@ -16,6 +16,6 @@ <shape xmlns:android = "http://schemas.android.com/apk/res/android"> <size - android:width = "@dimen/overlay_action_chip_margin_start" + android:width = "@dimen/shelf_action_chip_margin_start" android:height = "0dp"/> </shape> diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml index f644584f747a..01b9f7e2e38a 100644 --- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml @@ -26,8 +26,8 @@ android:layout_height="match_parent"> android:paddingVertical="16dp" android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/rightGuideline" - app:layout_constraintStart_toStartOf="@+id/leftGuideline" + app:layout_constraintRight_toLeftOf="@+id/rightGuideline" + app:layout_constraintLeft_toLeftOf="@+id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topGuideline" /> <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper @@ -35,8 +35,8 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" tools:srcCompat="@tools:sample/avatars" /> @@ -47,6 +47,7 @@ android:layout_height="match_parent"> android:layout_gravity="center" android:contentDescription="@null" android:scaleType="fitXY" + android:importantForAccessibility="no" app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" app:layout_constraintEnd_toEndOf="@+id/biometric_icon" app:layout_constraintStart_toStartOf="@+id/biometric_icon" @@ -62,8 +63,8 @@ android:layout_height="match_parent"> android:paddingTop="24dp" android:fadeScrollbars="false" app:layout_constraintBottom_toTopOf="@+id/button_bar" - app:layout_constraintEnd_toStartOf="@+id/midGuideline" - app:layout_constraintStart_toStartOf="@id/leftGuideline" + app:layout_constraintRight_toLeftOf="@+id/midGuideline" + app:layout_constraintLeft_toLeftOf="@id/leftGuideline" app:layout_constraintTop_toTopOf="@+id/topGuideline"> <androidx.constraintlayout.widget.ConstraintLayout @@ -88,7 +89,7 @@ android:layout_height="match_parent"> android:layout_width="0dp" android:layout_height="wrap_content" android:textAlignment="viewStart" - android:paddingLeft="16dp" + android:paddingStart="16dp" app:layout_constraintBottom_toBottomOf="@+id/logo" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/logo" @@ -208,6 +209,7 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + app:guidelineUseRtl="false" app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" /> <androidx.constraintlayout.widget.Guideline @@ -215,6 +217,7 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + app:guidelineUseRtl="false" app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> <androidx.constraintlayout.widget.Guideline @@ -222,6 +225,7 @@ android:layout_height="match_parent"> android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" + app:guidelineUseRtl="false" app:layout_constraintGuide_begin="406dp" /> <androidx.constraintlayout.widget.Guideline diff --git a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml index 46b8e4665a22..8b9eabc5bd93 100644 --- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml @@ -33,8 +33,7 @@ layout="@layout/biometric_prompt_button_bar" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginBottom="40dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/bottomGuideline" app:layout_constraintEnd_toEndOf="@id/panel" app:layout_constraintStart_toStartOf="@id/panel" /> @@ -229,6 +228,7 @@ android:layout_gravity="center" android:contentDescription="@null" android:scaleType="fitXY" + android:importantForAccessibility="no" app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" app:layout_constraintEnd_toEndOf="@+id/biometric_icon" app:layout_constraintStart_toStartOf="@+id/biometric_icon" diff --git a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml index 4d2310a2a6ca..9f4ad0ec8677 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_button_bar.xml @@ -23,11 +23,12 @@ <!-- Negative Button, reserved for app --> <Button android:id="@+id/button_negative" - style="@style/Widget.Dialog.Button.BorderButton" + style="@style/AuthCredentialNegativeButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginLeft="24dp" + android:layout_marginStart="24dp" + android:layout_marginBottom="8dp" android:ellipsize="end" android:maxLines="2" android:visibility="invisible" @@ -37,11 +38,12 @@ <!-- Cancel Button, replaces negative button when biometric is accepted --> <Button android:id="@+id/button_cancel" - style="@style/Widget.Dialog.Button.BorderButton" + style="@style/AuthCredentialNegativeButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginLeft="24dp" + android:layout_marginStart="24dp" + android:layout_marginBottom="8dp" android:text="@string/cancel" android:visibility="invisible" app:layout_constraintBottom_toBottomOf="parent" @@ -50,11 +52,12 @@ <!-- "Use Credential" Button, replaces if device credential is allowed --> <Button android:id="@+id/button_use_credential" - style="@style/Widget.Dialog.Button.BorderButton" + style="@style/AuthCredentialNegativeButtonStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginLeft="24dp" + android:layout_marginStart="24dp" + android:layout_marginBottom="8dp" android:visibility="invisible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> @@ -66,7 +69,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginRight="24dp" + android:layout_marginEnd="24dp" + android:layout_marginBottom="8dp" android:ellipsize="end" android:maxLines="2" android:text="@string/biometric_dialog_confirm" @@ -81,7 +85,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginRight="24dp" + android:layout_marginEnd="24dp" + android:layout_marginBottom="8dp" android:ellipsize="end" android:maxLines="2" android:text="@string/biometric_dialog_try_again" diff --git a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml index d51fe5849133..9b5b59fc116f 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml @@ -31,8 +31,7 @@ layout="@layout/biometric_prompt_button_bar" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginBottom="40dp" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toBottomOf="@id/bottomGuideline" app:layout_constraintEnd_toEndOf="@id/panel" app:layout_constraintStart_toStartOf="@id/panel" /> @@ -222,6 +221,7 @@ android:layout_gravity="center" android:contentDescription="@null" android:scaleType="fitXY" + android:importantForAccessibility="no" app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" app:layout_constraintEnd_toEndOf="@+id/biometric_icon" app:layout_constraintStart_toStartOf="@+id/biometric_icon" 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 new file mode 100644 index 000000000000..65005f840598 --- /dev/null +++ b/packages/SystemUI/res/layout/clipboard_overlay2.xml @@ -0,0 +1,202 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2021 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<com.android.systemui.clipboardoverlay.ClipboardOverlayView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/clipboard_ui" + android:theme="@style/FloatingOverlay" + android:alpha="0" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@string/clipboard_overlay_window_name"> + <!-- Min edge spacing guideline off of which the preview and actions can be anchored (without + this we'd need to express margins as the sum of two different dimens). --> + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/min_edge_guideline" + app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing" + android:orientation="vertical"/> + <!-- Negative horizontal margin because this container background must render beyond the thing + it's constrained by (the actions themselves). --> + <FrameLayout + android:id="@+id/actions_container_background" + android:visibility="gone" + android:layout_height="0dp" + android:layout_width="0dp" + android:elevation="4dp" + android:background="@drawable/shelf_action_chip_container_background" + android:layout_marginStart="@dimen/negative_overlay_action_container_minimum_edge_spacing" + android:layout_marginEnd="@dimen/negative_overlay_action_container_minimum_edge_spacing" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" + app:layout_constraintStart_toStartOf="@id/min_edge_guideline" + app:layout_constraintTop_toTopOf="@id/actions_container" + app:layout_constraintEnd_toEndOf="@id/actions_container" + app:layout_constraintBottom_toBottomOf="parent"/> + <HorizontalScrollView + android:id="@+id/actions_container" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal" + android:paddingEnd="@dimen/overlay_action_container_padding_end" + android:paddingVertical="@dimen/overlay_action_container_padding_vertical" + android:elevation="4dp" + android:scrollbars="none" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintWidth_percent="1.0" + app:layout_constraintWidth_max="wrap" + app:layout_constraintStart_toEndOf="@+id/preview_border" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"> + <LinearLayout + android:id="@+id/actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingStart="@dimen/shelf_action_chip_margin_start" + android:showDividers="middle" + android:divider="@drawable/shelf_action_chip_divider" + android:animateLayoutChanges="true"> + <include layout="@layout/shelf_action_chip" + android:id="@+id/share_chip"/> + <include layout="@layout/shelf_action_chip" + android:id="@+id/remote_copy_chip"/> + </LinearLayout> + </HorizontalScrollView> + <View + android:id="@+id/preview_border" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="@dimen/overlay_preview_container_margin" + android:layout_marginTop="@dimen/overlay_border_width_neg" + android:layout_marginEnd="@dimen/overlay_border_width_neg" + android:layout_marginBottom="@dimen/overlay_preview_container_margin" + android:elevation="7dp" + android:background="@drawable/overlay_border" + app:layout_constraintStart_toStartOf="@id/min_edge_guideline" + app:layout_constraintTop_toTopOf="@id/clipboard_preview" + app:layout_constraintEnd_toEndOf="@id/clipboard_preview" + app:layout_constraintBottom_toBottomOf="@id/actions_container_background"/> + <FrameLayout + android:id="@+id/clipboard_preview" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/overlay_border_width" + android:layout_marginBottom="@dimen/overlay_border_width" + android:layout_gravity="center" + android:elevation="7dp" + android:background="@drawable/overlay_preview_background" + android:clipChildren="true" + android:clipToOutline="true" + android:clipToPadding="true" + app:layout_constraintStart_toStartOf="@id/preview_border" + app:layout_constraintBottom_toBottomOf="@id/preview_border"> + <TextView android:id="@+id/text_preview" + android:textFontWeight="500" + android:padding="8dp" + android:gravity="center|start" + android:ellipsize="end" + android:autoSizeTextType="uniform" + android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font" + android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font" + android:textColor="?attr/overlayButtonTextColor" + android:textColorLink="?attr/overlayButtonTextColor" + android:background="?androidprv:attr/colorAccentSecondary" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_height="@dimen/clipboard_preview_size"/> + <ImageView + android:id="@+id/image_preview" + android:scaleType="fitCenter" + android:adjustViewBounds="true" + android:contentDescription="@string/clipboard_image_preview" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + <TextView + android:id="@+id/hidden_preview" + android:visibility="gone" + android:textFontWeight="500" + android:padding="8dp" + android:gravity="center" + android:textSize="14sp" + android:textColor="?attr/overlayButtonTextColor" + android:background="?androidprv:attr/colorAccentSecondary" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_height="@dimen/clipboard_preview_size"/> + </FrameLayout> + <LinearLayout + android:id="@+id/minimized_preview" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone" + android:elevation="7dp" + android:padding="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal" + android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom" + android:background="@drawable/clipboard_minimized_background"> + <ImageView + android:src="@drawable/ic_content_paste" + android:tint="?attr/overlayButtonTextColor" + android:layout_width="24dp" + android:layout_height="24dp"/> + <ImageView + android:src="@*android:drawable/ic_chevron_end" + android:tint="?attr/overlayButtonTextColor" + android:layout_width="24dp" + android:layout_height="24dp" + android:paddingEnd="-8dp" + android:paddingStart="-4dp"/> + </LinearLayout> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/clipboard_content_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:barrierDirection="top" + app:constraint_referenced_ids="clipboard_preview,minimized_preview"/> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/clipboard_content_end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:barrierDirection="end" + app:constraint_referenced_ids="clipboard_preview,minimized_preview"/> + <FrameLayout + android:id="@+id/dismiss_button" + android:layout_width="@dimen/overlay_dismiss_button_tappable_size" + android:layout_height="@dimen/overlay_dismiss_button_tappable_size" + android:elevation="10dp" + android:visibility="gone" + android:alpha="0" + app:layout_constraintStart_toEndOf="@id/clipboard_content_end" + app:layout_constraintEnd_toEndOf="@id/clipboard_content_end" + app:layout_constraintTop_toTopOf="@id/clipboard_content_top" + app:layout_constraintBottom_toTopOf="@id/clipboard_content_top" + android:contentDescription="@string/clipboard_dismiss_description"> + <ImageView + android:id="@+id/dismiss_image" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_margin="@dimen/overlay_dismiss_button_margin" + android:background="@drawable/circular_background" + android:backgroundTint="?androidprv:attr/materialColorPrimaryFixedDim" + android:tint="?androidprv:attr/materialColorOnPrimaryFixed" + android:padding="4dp" + android:src="@drawable/ic_close"/> + </FrameLayout> +</com.android.systemui.clipboardoverlay.ClipboardOverlayView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/contrast_dialog.xml b/packages/SystemUI/res/layout/contrast_dialog.xml deleted file mode 100644 index 8e885cf39e2b..000000000000 --- a/packages/SystemUI/res/layout/contrast_dialog.xml +++ /dev/null @@ -1,127 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - --> - -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <Space - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1"/> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <FrameLayout - android:id="@+id/contrast_button_standard" - android:layout_width="@dimen/contrast_dialog_button_total_size" - android:layout_height="@dimen/contrast_dialog_button_total_size" - android:background="@drawable/contrast_dialog_button_background"> - - <ImageView - android:layout_gravity="center" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_contrast_standard"/> - </FrameLayout> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing" - android:gravity="center_horizontal|top" - android:textSize="@dimen/contrast_dialog_button_text_size" - android:text="@string/quick_settings_contrast_standard" - android:textColor="?androidprv:attr/textColorPrimary"/> - </LinearLayout> - - <Space - android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing" - android:layout_height="match_parent" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <FrameLayout - android:id="@+id/contrast_button_medium" - android:layout_width="@dimen/contrast_dialog_button_total_size" - android:layout_height="@dimen/contrast_dialog_button_total_size" - android:background="@drawable/contrast_dialog_button_background"> - - <ImageView - android:layout_gravity="center" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_contrast_medium"/> - </FrameLayout> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing" - android:gravity="center_horizontal|top" - android:textSize="@dimen/contrast_dialog_button_text_size" - android:text="@string/quick_settings_contrast_medium" - android:textColor="?androidprv:attr/textColorPrimary"/> - </LinearLayout> - - <Space - android:layout_width="@dimen/contrast_dialog_button_horizontal_spacing" - android:layout_height="match_parent" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <FrameLayout - android:id="@+id/contrast_button_high" - android:layout_width="@dimen/contrast_dialog_button_total_size" - android:layout_height="@dimen/contrast_dialog_button_total_size" - android:background="@drawable/contrast_dialog_button_background"> - - <ImageView - android:layout_gravity="center" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:src="@drawable/ic_contrast_high"/> - - </FrameLayout> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/contrast_dialog_button_text_spacing" - android:gravity="center_horizontal|top" - android:textSize="@dimen/contrast_dialog_button_text_size" - android:text="@string/quick_settings_contrast_high" - android:textColor="?androidprv:attr/textColorPrimary"/> - </LinearLayout> - - <Space - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1"/> -</LinearLayout> diff --git a/packages/SystemUI/res/layout/dream_overlay_container.xml b/packages/SystemUI/res/layout/dream_overlay_container.xml index 19fb874ea2be..4234fca55e3c 100644 --- a/packages/SystemUI/res/layout/dream_overlay_container.xml +++ b/packages/SystemUI/res/layout/dream_overlay_container.xml @@ -21,6 +21,19 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + <ImageView + android:id="@+id/glanceable_hub_handle" + android:layout_width="4dp" + android:layout_height="220dp" + android:layout_centerVertical="true" + android:layout_marginEnd="12dp" + android:background="@drawable/hub_handle" + android:visibility="gone" + android:contentDescription="UI indicator for swiping open the glanceable hub" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/dream_overlay_content" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/ongoing_call_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml index 6a0217ec5fe8..a33be12a655a 100644 --- a/packages/SystemUI/res/layout/ongoing_call_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml @@ -17,43 +17,45 @@ the chip. --> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/ongoing_call_chip" + android:id="@+id/ongoing_activity_chip" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical|start" android:layout_marginStart="5dp" > - <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer - android:id="@+id/ongoing_call_chip_background" + <!-- TODO(b/332662551): Update this content description when this supports more than just + phone calls. --> + <com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer + android:id="@+id/ongoing_activity_chip_background" android:layout_width="wrap_content" android:layout_height="@dimen/ongoing_appops_chip_height" android:layout_gravity="center_vertical" android:gravity="center" - android:background="@drawable/ongoing_call_chip_bg" - android:paddingStart="@dimen/ongoing_call_chip_side_padding" - android:paddingEnd="@dimen/ongoing_call_chip_side_padding" + android:background="@drawable/ongoing_activity_chip_bg" + android:paddingStart="@dimen/ongoing_activity_chip_side_padding" + android:paddingEnd="@dimen/ongoing_activity_chip_side_padding" android:contentDescription="@string/ongoing_phone_call_content_description" android:minWidth="@dimen/min_clickable_item_size" > <ImageView android:src="@*android:drawable/ic_phone" - android:layout_width="@dimen/ongoing_call_chip_icon_size" - android:layout_height="@dimen/ongoing_call_chip_icon_size" + android:layout_width="@dimen/ongoing_activity_chip_icon_size" + android:layout_height="@dimen/ongoing_activity_chip_icon_size" android:tint="?android:attr/colorPrimary" /> - <com.android.systemui.statusbar.phone.ongoingcall.OngoingCallChronometer - android:id="@+id/ongoing_call_chip_time" + <com.android.systemui.statusbar.chips.ui.view.ChipChronometer + android:id="@+id/ongoing_activity_chip_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" android:gravity="center|start" - android:paddingStart="@dimen/ongoing_call_chip_icon_text_padding" + android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding" android:textAppearance="@android:style/TextAppearance.Material.Small" android:fontFamily="@*android:string/config_headlineFontFamily" android:textColor="?android:attr/colorPrimary" /> - </com.android.systemui.statusbar.phone.ongoingcall.OngoingCallBackgroundContainer> + </com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer> </FrameLayout> diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml index 7adfa6ca1c29..796def369708 100644 --- a/packages/SystemUI/res/layout/screenshot_shelf.xml +++ b/packages/SystemUI/res/layout/screenshot_shelf.xml @@ -23,43 +23,14 @@ <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/screenshot_static" android:layout_width="match_parent" - android:layout_height="match_parent"> - <FrameLayout - android:id="@+id/actions_container_background" - android:visibility="gone" - android:layout_height="wrap_content" - 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" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toTopOf="@id/guideline" - > - <HorizontalScrollView - android:id="@+id/actions_container" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical" - android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start" - android:background="@drawable/shelf_action_container_clipping_shape" - android:clipToOutline="true" - android:scrollbars="none"> - <LinearLayout - android:id="@+id/screenshot_actions" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:showDividers="middle" - android:divider="@drawable/shelf_action_chip_divider" - android:animateLayoutChanges="true" - /> - </HorizontalScrollView> - </FrameLayout> + android:layout_height="match_parent" + android:clipChildren="false" + android:clipToPadding="false"> <View 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" @@ -84,6 +55,52 @@ android:clickable="true" app:layout_constraintStart_toStartOf="@id/screenshot_preview_border" app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/> + <!-- Keep the same dimension with screenshot_preview. --> + <ImageView + android:id="@+id/screenshot_preview_blur" + android:layout_width="@dimen/overlay_x_scale" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/overlay_border_width" + android:layout_marginBottom="@dimen/overlay_border_width" + android:layout_gravity="center" + android:elevation="4dp" + android:contentDescription="@string/screenshot_edit_description" + android:scaleType="fitEnd" + android:background="@drawable/overlay_preview_background" + android:adjustViewBounds="true" + android:visibility="invisible" + app:layout_constraintStart_toStartOf="@id/screenshot_preview_border" + app:layout_constraintBottom_toBottomOf="@id/screenshot_preview_border"/> + <!-- Action bar should be drawn on top of the thumbnail --> + <FrameLayout + android:id="@+id/actions_container_background" + android:visibility="gone" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:elevation="4dp" + android:background="@drawable/shelf_action_chip_container_background" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintBottom_toTopOf="@id/guideline" + > + <HorizontalScrollView + android:id="@+id/actions_container" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginVertical="@dimen/overlay_action_container_padding_vertical" + android:layout_marginHorizontal="@dimen/overlay_action_chip_margin_start" + android:background="@drawable/shelf_action_container_clipping_shape" + android:clipToOutline="true" + android:scrollbars="none"> + <LinearLayout + android:id="@+id/screenshot_actions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:showDividers="middle" + android:divider="@drawable/shelf_action_chip_divider" + android:animateLayoutChanges="true" + android:orientation="horizontal" /> + </HorizontalScrollView> + </FrameLayout> <ImageView android:id="@+id/screenshot_badge" android:layout_width="56dp" @@ -118,11 +135,12 @@ android:id="@+id/screenshot_scrollable_preview" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:clipToOutline="true" android:scaleType="matrix" android:visibility="gone" app:layout_constraintStart_toStartOf="@id/screenshot_preview" app:layout_constraintTop_toTopOf="@id/screenshot_preview" - android:elevation="7dp"/> + android:elevation="3dp"/> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline" @@ -135,7 +153,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" @@ -153,6 +171,13 @@ </FrameLayout> </androidx.constraintlayout.widget.ConstraintLayout> <ImageView + android:id="@+id/screenshot_scrolling_scrim" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="gone" + android:clickable="true" + android:importantForAccessibility="no"/> + <ImageView android:id="@+id/screenshot_flash" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index 452bc317e2d5..4247c7eef0d0 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -99,7 +99,7 @@ android:gravity="center_vertical|start" /> - <include layout="@layout/ongoing_call_chip" /> + <include layout="@layout/ongoing_activity_chip" /> <com.android.systemui.statusbar.AlphaOptimizedFrameLayout android:id="@+id/notification_icon_area" diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml index 3861c98ed4b7..cc8f9c2ec305 100644 --- a/packages/SystemUI/res/values-af/strings.xml +++ b/packages/SystemUI/res/values-af/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Regtergrens <xliff:g id="PERCENT">%1$d</xliff:g> persent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Gestoor in <xliff:g id="APP">%1$s</xliff:g> in die werkprofiel"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Gestoor in <xliff:g id="APP">%1$s</xliff:g> in die privaat profiel"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Lêers"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> het hierdie skermskoot bespeur."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> en ander oop apps het hierdie skermskoot bespeur."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Maak legstukke op sluitskerm toe"</string> <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> + <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> @@ -623,10 +629,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"Stelsel-UI-ontvanger"</string> <string name="status_bar" msgid="4357390266055077437">"Statusbalk"</string> <string name="demo_mode" msgid="263484519766901593">"Stelsel-UI-demonstrasiemodus"</string> @@ -654,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelliet, swak verbinding"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliet, goeie toestand"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliet, verbinding is beskikbaar"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelliet-SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Werkprofiel"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Pret vir party mense, maar nie vir almal nie"</string> <string name="tuner_warning" msgid="1861736288458481650">"Stelsel-UI-ontvanger gee jou ekstra maniere om die Android-gebruikerkoppelvlak in te stel en te pasmaak. Hierdie eksperimentele kenmerke kan in toekomstige uitreikings verander, breek of verdwyn. Gaan versigtig voort."</string> @@ -862,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Voeg teël by"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Skuif na <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Voeg by posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisie is ongeldig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisie <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Teël is bygevoeg"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Teël is verwyder"</string> diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml index 9f9d3c5dfacd..b00dc78d4de4 100644 --- a/packages/SystemUI/res/values-am/strings.xml +++ b/packages/SystemUI/res/values-am/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"የሥራ መተግበሪያዎች ከቆሙበት ይቀጥሉ?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"ከቆመበት ቀጥል"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ምግብሮችን በማያ ገጽ ቁልፍ ላይ ዝጋ"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"ከሳተላይት ጋር ተገናኝቷል"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"ከሳተላይት ጋር አልተገናኘም"</string> + <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> @@ -861,6 +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> + <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-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml index 189722844bd1..d4981549654f 100644 --- a/packages/SystemUI/res/values-ar/strings.xml +++ b/packages/SystemUI/res/values-ar/strings.xml @@ -97,6 +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> + <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> @@ -161,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> @@ -446,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> @@ -459,6 +462,11 @@ <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> + <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_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> @@ -623,10 +631,18 @@ <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="system_ui_tuner" msgid="1471348823289954729">"أداة ضبط واجهة مستخدم النظام"</string> <string name="status_bar" msgid="4357390266055077437">"شريط الحالة"</string> <string name="demo_mode" msgid="263484519766901593">"وضع تجريبي لواجهة مستخدم النظام"</string> @@ -654,8 +670,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"تم الاتصال بالقمر الصناعي"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"لم يتم الاتصال بالقمر الصناعي"</string> + <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> @@ -860,6 +875,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> + <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-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml index ab19d565e92b..daefd834d85d 100644 --- a/packages/SystemUI/res/values-as/strings.xml +++ b/packages/SystemUI/res/values-as/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"কাম সম্পৰ্কীয় এপ্ আনপজ কৰিবনে?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"আনপজ কৰক"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"লক স্ক্ৰীনত ৱিজেট বন্ধ কৰক"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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">"ছিষ্টেমৰ UI প্ৰদৰ্শন ম\'ড"</string> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"উপগ্ৰহৰ সৈতে সংযোগ কৰা হৈছে"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"উপগ্ৰহৰ সৈতে সংযোগ কৰা হোৱা নাই"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"উপগ্ৰহ SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"কিছুমানৰ বাবে আমোদজনক হয় কিন্তু সকলোৰে বাবে নহয়"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tunerএ আপোনাক Android ব্যৱহাৰকাৰী ইণ্টাৰফেইচ সলনি কৰিবলৈ আৰু নিজৰ উপযোগিতা অনুসৰি ব্যৱহাৰ কৰিবলৈ অতিৰিক্ত সুবিধা প্ৰদান কৰে। এই পৰীক্ষামূলক সুবিধাসমূহ সলনি হ\'ব পাৰে, সেইবোৰে কাম নকৰিব পাৰে বা আগন্তুক সংস্কৰণসমূহত সেইবোৰ অন্তৰ্ভুক্ত কৰা নহ’ব পাৰে। সাৱধানেৰে আগবাঢ়ক।"</string> @@ -861,6 +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> + <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-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml index 62164a5d13c3..399e621f4fc7 100644 --- a/packages/SystemUI/res/values-az/strings.xml +++ b/packages/SystemUI/res/values-az/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sərhəd <xliff:g id="PERCENT">%1$d</xliff:g> faiz"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"İş profilində <xliff:g id="APP">%1$s</xliff:g> tətbiqində saxlanıb"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Şəxsi profildə <xliff:g id="APP">%1$s</xliff:g> tətbiqində yadda saxlanılıb"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> bu skrinşotu aşkarladı."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> və digər açıq tətbiqlər bu skrinşotu aşkarladı."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Kilid ekranında vidcetləri bağlayın"</string> <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> + <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> @@ -623,10 +629,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Status paneli"</string> <string name="demo_mode" msgid="263484519766901593">"Sistem interfeysi: demorejim"</string> @@ -654,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Peyk, bağlantı zəifdir"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Peyk, bağlantı yaxşıdır"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Peyk, bağlantı var"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Təcili peyk bağlantısı"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"İş profili"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Hamı üçün deyil, bəziləri üçün əyləncəli"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner Android istifadəçi interfeysini dəyişdirmək və fərdiləşdirmək üçün Sizə ekstra yollar təklif edir."</string> @@ -862,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lövhə əlavə edin"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyinə köçürün"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyinə əlavə edin"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Mövqe yanlışdır."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> mövqeyi"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Mozaik əlavə edilib"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Mozaik silinib"</string> diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml index 392ca53df142..1605666006c9 100644 --- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml +++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Leva ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna ivica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na poslovnom profilu"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na privatnom profilu"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je otkrila ovaj snimak ekrana."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije su otkrile ovaj snimak ekrana."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Uključiti poslovne aplikacije?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Ponovo aktiviraj"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zatvorite vidžete na zaključanom ekranu"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, veza je loša"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, veza je dobra"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Povezano sa satelitom"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Nije povezano sa satelitom"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Hitna pomoć preko satelita"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Poslovni profil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string> <string name="tuner_warning" msgid="1861736288458481650">"Tjuner za korisnički interfejs sistema vam pruža dodatne načine za podešavanje i prilagođavanje Android korisničkog interfejsa. Ove eksperimentalne funkcije mogu da se promene, otkažu ili nestanu u budućim izdanjima. Budite oprezni."</string> @@ -861,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodajte pločicu"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premestite na <xliff:g id="POSITION">%1$d</xliff:g>. poziciju"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajte na <xliff:g id="POSITION">%1$d</xliff:g>. poziciju"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozicija je nevažeća."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozicija"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Pločica je dodata"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Pločica je uklonjena"</string> diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml index 9977857a9324..89a4701a3ba3 100644 --- a/packages/SystemUI/res/values-be/strings.xml +++ b/packages/SystemUI/res/values-be/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Уключыць працоўныя праграмы?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Уключыць"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Закрыць віджэты на экране блакіроўкі"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <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> @@ -863,6 +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> + <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 5b97cb444eca..bdd9fd45e0b9 100644 --- a/packages/SystemUI/res/values-bg/strings.xml +++ b/packages/SystemUI/res/values-bg/strings.xml @@ -97,6 +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> + <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">"Files"</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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Установена е връзка със сателит"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Не е установена връзка със сателит"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS чрез сателит"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Потребителски профил в Work"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Забавно – но не за всички"</string> <string name="tuner_warning" msgid="1861736288458481650">"Тунерът на системния потребителски интерфейс ви предоставя допълнителни възможности за прецизиране и персонализиране на практическата работа с Android. Тези експериментални функции може да се променят, повредят или да изчезнат в бъдещите версии. Действайте внимателно."</string> @@ -860,6 +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> + <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-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml index 322b72384181..2c47b129b632 100644 --- a/packages/SystemUI/res/values-bn/strings.xml +++ b/packages/SystemUI/res/values-bn/strings.xml @@ -97,6 +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> percent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"অফিস প্রোফাইলের মধ্যে <xliff:g id="APP">%1$s</xliff:g>-এ সেভ করা হয়েছে"</string> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"অফিসের অ্যাপ আনপজ করতে চান?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"আনপজ করুন"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"লক স্ক্রিনে উইজেট বন্ধ করুন"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <string name="media_output_title_without_playing" msgid="3825663683169305013">"অডিও এতে প্লে করা হবে"</string> + <string name="media_output_title_ongoing_call" msgid="208426888064112006">"কল চালু আছে"</string> <string name="system_ui_tuner" msgid="1471348823289954729">"সিস্টেম UI টিউনার"</string> <string name="status_bar" msgid="4357390266055077437">"স্ট্যাটাস বার"</string> <string name="demo_mode" msgid="263484519766901593">"সিস্টেম UI ডেমো মোড"</string> @@ -655,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"স্যাটেলাইট SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"কাজের প্রোফাইল"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"কিছু ব্যক্তির জন্য মজাদার কিন্তু সকলের জন্য নয়"</string> <string name="tuner_warning" msgid="1861736288458481650">"এই পরীক্ষামূলক বৈশিষ্ট্যগুলি ভবিষ্যতের সংস্করণগুলির মধ্যে পরিবর্তিত, বিভাজিত এবং অদৃশ্য হয়ে যেতে পারে৷ সাবধানতার সাথে এগিয়ে যান৷ সিস্টেম UI টিউনার আপনাকে Android ব্যবহারকারী ইন্টারফেসের সূক্ষ্ম সমন্বয় এবং কাস্টমাইজ করার অতিরিক্ত উপায়গুলি প্রদান করে৷"</string> @@ -863,6 +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> + <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-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml index 63340d7731bd..8621b43bdafd 100644 --- a/packages/SystemUI/res/values-bs/strings.xml +++ b/packages/SystemUI/res/values-bs/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Lijeva granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desna granica <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na radnom profilu"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Sačuvano je u aplikaciji <xliff:g id="APP">%1$s</xliff:g> na privatnom profilu"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fajlovi"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je otkrila ovaj snimak ekrana."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije su otkrile ovaj snimak ekrana."</string> @@ -152,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> @@ -266,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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Pokrenuti poslovne aplikacije?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Ponovo pokreni"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zatvaranje vidžeta na zaključanom ekranu"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> - <string name="media_output_label_title" msgid="872824698593182505">"Reproduciranje: <xliff:g id="LABEL">%s</xliff:g>"</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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, slaba veza"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra veza"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Hitna pomoć putem satelita"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Radni profil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string> <string name="tuner_warning" msgid="1861736288458481650">"Podešavač za korisnički interfejs sistema vam omogućava dodatne načine da podesite i prilagodite Androidov interfejs. Ove eksperimentalne funkcije se u budućim verzijama mogu mijenjati, kvariti ili nestati. Budite oprezni."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodavanje kartice"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pomjeranje u položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje u položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nevažeći položaj."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartica je dodana"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartica je uklonjena"</string> diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml index 341dca54280a..6ebfd86b85ba 100644 --- a/packages/SystemUI/res/values-ca/strings.xml +++ b/packages/SystemUI/res/values-ca/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marge esquerre <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marge dret <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"S\'ha desat al perfil de treball de <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"S\'ha desat a <xliff:g id="APP">%1$s</xliff:g> al perfil privat"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxers"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ha detectat aquesta captura de pantalla."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i altres aplicacions obertes han detectat aquesta captura de pantalla."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Reactivar les apps de treball?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Reactiva"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Tanca els widgets a la pantalla de bloqueig"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satèl·lit, connexió deficient"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satèl·lit, bona connexió"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satèl·lit, connexió disponible"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS per satèl·lit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de treball"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Diversió per a uns quants, però no per a tothom"</string> <string name="tuner_warning" msgid="1861736288458481650">"El Personalitzador d\'interfície d\'usuari presenta opcions addicionals per canviar i personalitzar la interfície d\'usuari d\'Android. És possible que aquestes funcions experimentals canviïn, deixin de funcionar o desapareguin en versions futures. Continua amb precaució."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Afegeix una icona"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mou a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Afegeix a la posició <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"La posició no és vàlida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posició <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"El mosaic s\'ha afegit"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"El mosaic s\'ha suprimit"</string> @@ -1195,7 +1205,7 @@ <string name="wifi_wont_autoconnect_for_now" msgid="5782282612749867762">"Per ara la Wi‑Fi no es connectarà automàticament"</string> <string name="see_all_networks" msgid="3773666844913168122">"Mostra-ho tot"</string> <string name="to_switch_networks_disconnect_ethernet" msgid="6698111101156951955">"Per canviar de xarxa, desconnecta la connexió Ethernet"</string> - <string name="wifi_scan_notify_message" msgid="3753839537448621794">"Per millorar l\'experiència del dispositiu, les aplicacions i els serveis poden cercar xarxes Wi‑Fi en qualsevol moment, fins i tot quan la Wi‑Fi estigui desactivada. Pots canviar aquesta opció a la configuració de cerca de xarxes Wi‑Fi. "<annotation id="link">"Canvia-la"</annotation>"."</string> + <string name="wifi_scan_notify_message" msgid="3753839537448621794">"Per millorar l\'experiència amb el dispositiu, les aplicacions i els serveis poden continuar cercant xarxes Wi‑Fi en qualsevol moment, fins i tot amb la Wi‑Fi desactivada. Pots canviar aquesta opció a la configuració de cerca de xarxes Wi‑Fi. "<annotation id="link">"Canvia-la"</annotation>"."</string> <string name="turn_off_airplane_mode" msgid="8425587763226548579">"Desactiva el mode d\'avió"</string> <string name="qs_tile_request_dialog_text" msgid="3501359944139877694">"<xliff:g id="APPNAME">%1$s</xliff:g> vol afegir la icona següent a la configuració ràpida"</string> <string name="qs_tile_request_dialog_add" msgid="4888460910694986304">"Afegeix la icona"</string> diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml index 0598097dc65b..7d2202da95c9 100644 --- a/packages/SystemUI/res/values-cs/strings.xml +++ b/packages/SystemUI/res/values-cs/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Levý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Pravý okraj <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Uloženo v aplikaci <xliff:g id="APP">%1$s</xliff:g> v pracovním profilu"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Uloženo do aplikace <xliff:g id="APP">%1$s</xliff:g> v soukromém profilu"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Soubory"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikace <xliff:g id="APPNAME">%1$s</xliff:g> objevila tento snímek obrazovky."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> a ostatní otevřené aplikace objevily tento snímek obrazovky."</string> @@ -268,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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Zrušit pozastavení pracovních aplikací?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Zrušit pozastavení"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zavřít widgety na obrazovce uzamčení"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> - <string name="media_output_label_title" msgid="872824698593182505">"Přehrávání <xliff:g id="LABEL">%s</xliff:g> na"</string> - <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk se přehraje přes"</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> + <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> @@ -655,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, špatné připojení"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobré připojení"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, připojení je k dispozici"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Připojeno k satelitu"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Nepřipojeno k satelitu"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS přes satelit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Pracovní profil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Zábava, která není pro každého"</string> <string name="tuner_warning" msgid="1861736288458481650">"Nástroj na ladění uživatelského rozhraní systému vám nabízí další způsoby, jak si vyladit a přizpůsobit uživatelské rozhraní Android. Tyto experimentální funkce mohou v dalších verzích chybět, nefungovat nebo být změněny. Postupujte proto prosím opatrně."</string> @@ -861,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Přidat dlaždici"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Přesunout na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Přidat dlaždici na pozici <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozice není platná."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozice <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Karta byla přidána"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Karta byla odstraněna"</string> diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml index bee35637f090..ec03938ad7e2 100644 --- a/packages/SystemUI/res/values-da/strings.xml +++ b/packages/SystemUI/res/values-da/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Højre kant: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Gemt i <xliff:g id="APP">%1$s</xliff:g> på arbejdsprofilen"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Gemt i <xliff:g id="APP">%1$s</xliff:g> på den private profil"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> har registreret dette screenshot."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> og andre åbne apps har registreret dette screenshot."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Vil du genoptage arbejdsapps?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Genoptag"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Luk widgets på låseskærmen"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -589,7 +594,7 @@ <string name="screen_pinning_negative" msgid="6882816864569211666">"Nej tak"</string> <string name="screen_pinning_start" msgid="7483998671383371313">"Appen er fastgjort"</string> <string name="screen_pinning_exit" msgid="4553787518387346893">"Appen er frigjort"</string> - <string name="stream_voice_call" msgid="7468348170702375660">"Ring op"</string> + <string name="stream_voice_call" msgid="7468348170702375660">"Opkald"</string> <string name="stream_system" msgid="7663148785370565134">"System"</string> <string name="stream_ring" msgid="7550670036738697526">"Ringetone"</string> <string name="stream_music" msgid="2188224742361847580">"Medie"</string> @@ -624,10 +629,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"System UI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Statusbjælke"</string> <string name="demo_mode" msgid="263484519766901593">"Demotilstand for systemets brugerflade"</string> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellit – dårlig forbindelse"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit – god forbindelse"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit – forbindelsen er tilgængelig"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-meldinger via satellit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Arbejdsprofil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Sjovt for nogle, men ikke for alle"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner giver dig flere muligheder for at justere og tilpasse Android-brugerfladen. Disse eksperimentelle funktioner kan ændres, gå i stykker eller forsvinde i fremtidige udgivelser. Vær forsigtig, hvis du fortsætter."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tilføj felt"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flyt til <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Føj til lokation <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positionen er ugyldig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Lokation <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Feltet blev tilføjet"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Feltet blev fjernet"</string> diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml index e42436304e6c..d38ad3ae6a83 100644 --- a/packages/SystemUI/res/values-de/strings.xml +++ b/packages/SystemUI/res/values-de/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linker Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechter Rand <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"In <xliff:g id="APP">%1$s</xliff:g> im Arbeitsprofil gespeichert"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Im vertraulichen Profil in <xliff:g id="APP">%1$s</xliff:g> gespeichert"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dateien"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> hat diesen Screenshot erkannt."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> und andere geöffnete Apps haben diesen Screenshot erkannt."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Geschäftliche Apps nicht mehr pausieren?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Nicht mehr pausieren"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Widgets auf dem Sperrbildschirm schließen"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellit, Verbindung schlecht"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit, Verbindung gut"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit, Verbindung verfügbar"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Mit Satellit verbunden"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Nicht mit Satellit verbunden"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Notruf über Satellit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Arbeitsprofil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Für einige ein Vergnügen, aber nicht für alle"</string> <string name="tuner_warning" msgid="1861736288458481650">"Mit System UI Tuner erhältst du zusätzliche Möglichkeiten, die Android-Benutzeroberfläche anzupassen. Achtung: Diese Testfunktionen können sich ändern, abstürzen oder in zukünftigen Versionen verschwinden."</string> @@ -861,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Kachel hinzufügen"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Auf Position <xliff:g id="POSITION">%1$d</xliff:g> verschieben"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Zur Position <xliff:g id="POSITION">%1$d</xliff:g> hinzufügen"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position ist ungültig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ansicht hinzugefügt"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ansicht entfernt"</string> diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml index f8e0b53cd36d..c6dad44b10e9 100644 --- a/packages/SystemUI/res/values-el/strings.xml +++ b/packages/SystemUI/res/values-el/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Αναίρ. παύσης εφαρμ. εργασιών;"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Αναίρεση παύσης"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Κλείσιμο γραφικών στοιχείων στην οθόνη κλειδώματος"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Συνδέθηκε σε δορυφόρο"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Δεν συνδέθηκε σε δορυφόρο"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Δορυφορικό SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Προφίλ εργασίας"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Διασκέδαση για ορισμένους, αλλά όχι για όλους"</string> <string name="tuner_warning" msgid="1861736288458481650">"Το System UI Tuner σάς προσφέρει επιπλέον τρόπους για να τροποποιήσετε και να προσαρμόσετε τη διεπαφή χρήστη Android. Αυτές οι πειραματικές λειτουργίες ενδέχεται να τροποποιηθούν, να παρουσιάσουν σφάλματα ή να καταργηθούν σε μελλοντικές εκδόσεις. Συνεχίστε με προσοχή."</string> @@ -861,6 +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> + <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-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml index 5a18245ea44d..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rAU/strings.xml +++ b/packages/SystemUI/res/values-en-rAU/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the private profile"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Close widgets on lock screen"</string> <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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, poor connection"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string> @@ -862,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 8b71d4b5bf2d..fa007b67faee 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the private profile"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string> @@ -446,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> @@ -459,6 +461,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Close widgets on lock screen"</string> <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customize 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> + <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> @@ -623,10 +628,13 @@ <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> <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> @@ -654,8 +662,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, poor connection"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Connected to satellite"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Not connected to satellite"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customize the Android user interface. These experimental features may change, break, or disappear in future releases. Proceed with caution."</string> @@ -860,6 +867,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml index 5a18245ea44d..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rGB/strings.xml +++ b/packages/SystemUI/res/values-en-rGB/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the private profile"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Close widgets on lock screen"</string> <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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, poor connection"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string> @@ -862,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml index 5a18245ea44d..cbb22984c351 100644 --- a/packages/SystemUI/res/values-en-rIN/strings.xml +++ b/packages/SystemUI/res/values-en-rIN/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> per cent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the private profile"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Close widgets on lock screen"</string> <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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, poor connection"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customise the Android user interface. These experimental features may change, break or disappear in future releases. Proceed with caution."</string> @@ -862,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index f32b774bc080..248360bac608 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Left boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Right boundary <xliff:g id="PERCENT">%1$d</xliff:g> percent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the work profile"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saved in <xliff:g id="APP">%1$s</xliff:g> in the private profile"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detected this screenshot."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> and other open apps detected this screenshot."</string> @@ -446,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> @@ -459,6 +461,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Close widgets on lock screen"</string> <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Customize 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> + <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> @@ -623,10 +628,13 @@ <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> <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> @@ -654,8 +662,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, poor connection"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, good connection"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, connection available"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Connected to satellite"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Not connected to satellite"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work profile"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Fun for some but not for all"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner gives you extra ways to tweak and customize the Android user interface. These experimental features may change, break, or disappear in future releases. Proceed with caution."</string> @@ -860,6 +867,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Add tile"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Move to <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Add to position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position invalid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tile added"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tile removed"</string> diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml index 552f31fc8546..28230f1d573d 100644 --- a/packages/SystemUI/res/values-es-rUS/strings.xml +++ b/packages/SystemUI/res/values-es-rUS/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Límite izquierdo: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Límite derecho: <xliff:g id="PERCENT">%1$d</xliff:g> por ciento"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Se guardó en <xliff:g id="APP">%1$s</xliff:g> en el perfil de trabajo"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Guardada en <xliff:g id="APP">%1$s</xliff:g> en el perfil personal"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Archivos"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detectó que tomaste una captura de pantalla."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> y otras apps en ejecución detectaron que tomaste una captura de pantalla."</string> @@ -274,7 +275,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Guardado"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"desconectar"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"activar"</string> - <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Volver a activar automáticamente mañana"</string> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Activar automáticamente mañana"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Las funciones como Quick Share y Encontrar mi dispositivo usan Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"El Bluetooth se activará mañana a la mañana"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Uso compartido de audio"</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"¿Reanudar apps de trabajo?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Reanudar"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Cerrar widgets en la pantalla de bloqueo"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -614,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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, conexión inestable"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, buena conexión"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión disponible"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabajo"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Diversión para algunos, pero no para todos"</string> <string name="tuner_warning" msgid="1861736288458481650">"El sintonizador de IU del sistema te brinda más formas para editar y personalizar la interfaz de usuario de Android. Estas funciones experimentales pueden cambiar, dejar de funcionar o no incluirse en futuras versiones. Procede con precaución."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Agregar tarjeta"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Agregar a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición no válida"</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Se agregó la tarjeta"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Se quitó la tarjeta"</string> diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml index 10f480e11921..fd2ac8582ae4 100644 --- a/packages/SystemUI/res/values-es/strings.xml +++ b/packages/SystemUI/res/values-es/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite izquierdo"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> por ciento del límite derecho"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Se ha guardado en el perfil de trabajo de <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Se ha guardado en el perfil privado de <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Archivos"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ha detectado esta captura de pantalla."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> y otras aplicaciones abiertas han detectado esta captura de pantalla."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"¿Reactivar apps de trabajo?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Reactivar"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Cerrar widgets en la pantalla de bloqueo"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, mala conexión"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, buena conexión"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión disponible"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabajo"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Diversión solo para algunos"</string> <string name="tuner_warning" msgid="1861736288458481650">"El configurador de UI del sistema te ofrece otras formas de modificar y personalizar la interfaz de usuario de Android. Estas funciones experimentales pueden cambiar, fallar o desaparecer en futuras versiones. Te recomendamos que tengas cuidado."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Añadir recuadro"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Añadir a la posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición no válida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Recuadro añadido"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Recuadro quitado"</string> diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml index 0f98ce683a5d..7f97176012d7 100644 --- a/packages/SystemUI/res/values-et/strings.xml +++ b/packages/SystemUI/res/values-et/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasak piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Parem piir: <xliff:g id="PERCENT">%1$d</xliff:g> protsenti"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvestati tööprofiilil rakendusse <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salvestati privaatsel profiilil rakendusse <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> tuvastas selle ekraanipildi."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ja muud avatud rakendused tuvastasid selle ekraanipildi."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Lukustuskuva vidinate sulgemine"</string> <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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelliit, kehv ühendus"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliit, hea ühendus"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliit, ühendus on saadaval"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelliit-SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Tööprofiil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Kõik ei pruugi sellest rõõmu tunda"</string> <string name="tuner_warning" msgid="1861736288458481650">"Süsteemi kasutajaliidese tuuner pakub täiendavaid võimalusi Androidi kasutajaliidese muutmiseks ja kohandamiseks. Need katselised funktsioonid võivad muutuda, rikki minna või tulevastest versioonidest kaduda. Olge jätkamisel ettevaatlik."</string> @@ -862,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lisa paan"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Teisaldamine asendisse <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisamine asendisse <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Sobimatu asukoht."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Asend <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Paan on lisatud"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Paan on eemaldatud"</string> diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml index 393aa97b2b89..685b5921bcd0 100644 --- a/packages/SystemUI/res/values-eu/strings.xml +++ b/packages/SystemUI/res/values-eu/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Ezkerreko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Eskuineko ertza: ehuneko <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Laneko profilaren <xliff:g id="APP">%1$s</xliff:g> aplikazioan gorde da"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Profil pribatuaren <xliff:g id="APP">%1$s</xliff:g> aplikazioan gorde da"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fitxategiak"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioak pantaila-argazkia hauteman du."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> aplikazioak eta irekitako beste aplikazio batzuek pantaila-argazkia hauteman dute."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Laneko aplikazioak berraktibatu?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Berraktibatu"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Itxi pantaila blokeatuko widgetak"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelitea, konexio ahula"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelitea, konexio ona"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelitea, konexioa erabilgarri"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelite bidezko SOS komunikazioa"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Laneko profila"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Dibertsioa batzuentzat, baina ez guztientzat"</string> <string name="tuner_warning" msgid="1861736288458481650">"Sistemaren erabiltzaile-interfazearen konfiguratzaileak Android erabiltzaile-interfazea moldatzeko eta pertsonalizatzeko modu gehiago eskaintzen dizkizu. Baliteke eginbide esperimental horiek hurrengo kaleratzeetan aldatuta, etenda edo desagertuta egotea. Kontuz erabili."</string> @@ -773,8 +782,8 @@ <string name="group_system_go_back" msgid="2730322046244918816">"Egin atzera"</string> <string name="group_system_access_home_screen" msgid="4130366993484706483">"Joan orri nagusira"</string> <string name="group_system_overview_open_apps" msgid="5659958952937994104">"Ikusi azkenaldiko aplikazioak"</string> - <string name="group_system_cycle_forward" msgid="5478663965957647805">"Ikusi azken aplikazioak banan-banan (aurrerantz)"</string> - <string name="group_system_cycle_back" msgid="8194102916946802902">"Ikusi azken aplikazioak banan-banan (atzerantz)"</string> + <string name="group_system_cycle_forward" msgid="5478663965957647805">"Ikusi azkenaldiko aplikazioak banan-banan (aurrerantz)"</string> + <string name="group_system_cycle_back" msgid="8194102916946802902">"Ikusi azkenaldiko aplikazioak banan-banan (atzerantz)"</string> <string name="group_system_access_all_apps_search" msgid="1553588630154197469">"Ireki aplikazioen zerrenda"</string> <string name="group_system_access_system_settings" msgid="8731721963449070017">"Ireki ezarpenak"</string> <string name="group_system_access_google_assistant" msgid="7210074957915968110">"Ireki Laguntzailea"</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Gehitu lauza"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Eraman <xliff:g id="POSITION">%1$d</xliff:g>garren lekura"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Gehitu <xliff:g id="POSITION">%1$d</xliff:g>garren lekuan"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Kokapenak ez du balio."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>garren lekua"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Gehitu da lauza"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kendu da lauza"</string> diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml index 10ff0dbaaa38..d21fdd330c11 100644 --- a/packages/SystemUI/res/values-fa/strings.xml +++ b/packages/SystemUI/res/values-fa/strings.xml @@ -21,7 +21,7 @@ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4811759950673118541">"واسط کاربری سیستم"</string> <string name="battery_low_title" msgid="5319680173344341779">"«بهینهسازی باتری» روشن شود؟"</string> - <string name="battery_low_description" msgid="3282977755476423966">"<xliff:g id="PERCENTAGE">%s</xliff:g> از باتریتان باقی مانده است. «بهینهسازی باتری» زمینه تیره را روشن میکند، فعالیتهای پسزمینه را محدود میکند، و اعلانها را بهتأخیر میاندازد."</string> + <string name="battery_low_description" msgid="3282977755476423966">"<xliff:g id="PERCENTAGE">%s</xliff:g> از باتریتان باقی مانده است. «بهینهسازی باتری» زمینه تاریک را روشن میکند، فعالیتهای پسزمینه را محدود میکند، و اعلانها را بهتأخیر میاندازد."</string> <string name="battery_low_intro" msgid="5148725009653088790">"«بهینهسازی باتری» زمینه «تیره» را روشن میکند، فعالیتهای پسزمینه را محدود میکند، و اعلانها را بهتأخیر میاندازد."</string> <string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> باقی مانده است"</string> <string name="invalid_charger_title" msgid="938685362320735167">"ازطریق USB شارژ نمیشود"</string> @@ -97,6 +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> + <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">"Files"</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> @@ -187,7 +188,7 @@ <string name="biometric_dialog_last_pin_attempt_before_wipe_profile" msgid="545567685899091757">"اگر در تلاش بعدی پین نادرستی وارد کنید، نمایه کاری شما و دادههای آن حذف خواهند شد."</string> <string name="biometric_dialog_last_password_attempt_before_wipe_profile" msgid="8538032972389729253">"اگر در تلاش بعدی گذرواژه نادرستی وارد کنید، نمایه کاری شما و دادههای آن حذف خواهند شد."</string> <string name="biometric_re_enroll_dialog_confirm" msgid="3049858021857801836">"راهاندازی"</string> - <string name="biometric_re_enroll_dialog_cancel" msgid="93760939407091417">"اکنون نه"</string> + <string name="biometric_re_enroll_dialog_cancel" msgid="93760939407091417">"حالا نه"</string> <string name="biometric_re_enroll_notification_content" msgid="8685925877186288180">"این کار برای بهبود امنیت و عملکرد لازم است"</string> <string name="fingerprint_re_enroll_notification_title" msgid="4539432429683916604">"راهاندازی مجدد «قفلگشایی با اثر انگشت»"</string> <string name="fingerprint_re_enroll_notification_name" msgid="630798657797645704">"قفلگشایی با اثر انگشت"</string> @@ -336,7 +337,7 @@ <string name="quick_settings_night_secondary_label_until_sunrise" msgid="4063448287758262485">"تا طلوع"</string> <string name="quick_settings_night_secondary_label_on_at" msgid="3584738542293528235">"ساعت <xliff:g id="TIME">%s</xliff:g> روشن میشود"</string> <string name="quick_settings_secondary_label_until" msgid="1883981263191927372">"تا <xliff:g id="TIME">%s</xliff:g>"</string> - <string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"زمینه تیره"</string> + <string name="quick_settings_ui_mode_night_label" msgid="1398928270610780470">"زمینه تاریک"</string> <string name="quick_settings_dark_mode_secondary_label_battery_saver" msgid="4990712734503013251">"بهینهسازی باتری"</string> <string name="quick_settings_dark_mode_secondary_label_on_at_sunset" msgid="6017379738102015710">"غروب روشن میشود"</string> <string name="quick_settings_dark_mode_secondary_label_until_sunrise" msgid="4404885070316716472">"تا طلوع آفتاب"</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"مکث برنامههای کاری لغو شود؟"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"لغو مکث"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"بستن ابزارکها در صفحه قفل"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"به ماهواره متصل هستید"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"به ماهواره متصل نیستید"</string> + <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> @@ -861,6 +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> + <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-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml index bc761e7ea47f..8910bf320593 100644 --- a/packages/SystemUI/res/values-fi/strings.xml +++ b/packages/SystemUI/res/values-fi/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vasen reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oikea reuna <xliff:g id="PERCENT">%1$d</xliff:g> prosenttia"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Tallennettu työprofiiliin tässä sovelluksessa: <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Tallennettu yksityiseen profiiliin tässä sovelluksessa: <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> havaitsi tämän kuvakaappauksen."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ja jotkin muut sovellukset havaitsivat tämän kuvakaappauksen."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Laita työsovellukset päälle?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Laita päälle"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Sulje widgetit lukitusnäytöllä"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelliitti, huono yhteys"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliitti, hyvä yhteys"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliitti, yhteys saatavilla"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Yhteys satelliittiin"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Ei yhteyttä satelliittiin"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Työprofiili"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Ei sovellu kaikkien käyttöön"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner antaa lisämahdollisuuksia Android-käyttöliittymän muokkaamiseen. Nämä kokeelliset ominaisuudet voivat muuttua, lakata toimimasta tai kadota milloin tahansa. Jatka omalla vastuullasi."</string> @@ -861,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lisää kiekko"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Siirrä paikkaan <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lisää paikkaan <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Virheellinen sijainti."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Paikka <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kiekko lisätty"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kiekko poistettu"</string> @@ -916,7 +928,7 @@ <string name="mobile_data_text_format" msgid="6806501540022589786">"<xliff:g id="ID_1">%1$s</xliff:g> – <xliff:g id="ID_2">%2$s</xliff:g>"</string> <string name="mobile_carrier_text_format" msgid="8912204177152950766">"<xliff:g id="CARRIER_NAME">%1$s</xliff:g>, <xliff:g id="MOBILE_DATA_TYPE">%2$s</xliff:g>"</string> <string name="wifi_is_off" msgid="5389597396308001471">"Wi-Fi on pois päältä"</string> - <string name="bt_is_off" msgid="7436344904889461591">"Bluetooth ei ole käytössä"</string> + <string name="bt_is_off" msgid="7436344904889461591">"Bluetooth on pois päältä"</string> <string name="dnd_is_off" msgid="3185706903793094463">"Älä häiritse ‑tila on pois päältä"</string> <string name="dnd_is_on" msgid="7009368176361546279">"Älä häiritse ‑tila on käytössä"</string> <string name="qs_dnd_prompt_auto_rule" msgid="3535469468310002616">"Automaattinen sääntö otti käyttöön Älä häiritse ‑tilan (<xliff:g id="ID_1">%s</xliff:g>)."</string> diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml index f039c83b00ec..df5f59f80dd1 100644 --- a/packages/SystemUI/res/values-fr-rCA/strings.xml +++ b/packages/SystemUI/res/values-fr-rCA/strings.xml @@ -20,13 +20,13 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_label" msgid="4811759950673118541">"IU système"</string> - <string name="battery_low_title" msgid="5319680173344341779">"Activer l\'économiseur de pile?"</string> + <string name="battery_low_title" msgid="5319680173344341779">"Activer l\'Économiseur de pile?"</string> <string name="battery_low_description" msgid="3282977755476423966">"Il vous reste <xliff:g id="PERCENTAGE">%s</xliff:g> d\'autonomie. L\'économiseur de pile active le thème sombre, limite l\'activité en arrière-plan et retarde les notifications."</string> <string name="battery_low_intro" msgid="5148725009653088790">"L\'économiseur de pile active le thème sombre, limite l\'activité en arrière-plan et retarde les notifications."</string> <string name="battery_low_percent_format" msgid="4276661262843170964">"<xliff:g id="PERCENTAGE">%s</xliff:g> restants"</string> <string name="invalid_charger_title" msgid="938685362320735167">"Impossible de charger l\'appareil par USB"</string> <string name="invalid_charger_text" msgid="2339310107232691577">"Servez-vous du chargeur fourni avec votre appareil"</string> - <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Activer l\'économiseur de pile?"</string> + <string name="battery_saver_confirmation_title" msgid="1234998463717398453">"Activer l\'Économiseur de pile?"</string> <string name="battery_saver_confirmation_title_generic" msgid="2299231884234959849">"À propos du mode Économiseur de pile"</string> <string name="battery_saver_confirmation_ok" msgid="5042136476802816494">"Activer"</string> <string name="battery_saver_start_action" msgid="8353766979886287140">"Activer"</string> @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g> dans le profil professionnel"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g> dans le profil privé"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a détecté cette capture d\'écran."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> et d\'autres applications ouvertes ont détecté cette capture d\'écran."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fermer les widgets sur l\'écran de verrouillage"</string> <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> + <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> @@ -623,10 +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> + <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> @@ -654,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Connexion satellite faible"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Bonne connexion satellite"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Connexion satellite accessible"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Connecté au satellite"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Non connecté au satellite"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS par satellite"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil professionnel"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Divertissant pour certains, mais pas pour tous"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner vous propose de nouvelles manières d\'adapter et de personnaliser l\'interface utilisateur d\'Android. Ces fonctionnalités expérimentales peuvent être modifiées, cesser de fonctionner ou disparaître dans les versions futures. À utiliser avec prudence."</string> @@ -860,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ajouter la tuile"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Déplacer vers <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Position incorrecte."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tuile ajoutée"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tuile retirée"</string> @@ -1004,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 0df898887258..b02d547e08a8 100644 --- a/packages/SystemUI/res/values-fr/strings.xml +++ b/packages/SystemUI/res/values-fr/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite gauche : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite droite : <xliff:g id="PERCENT">%1$d</xliff:g> pour cent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g>, dans le profil professionnel"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Enregistré dans <xliff:g id="APP">%1$s</xliff:g> dans le profil privé"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fichiers"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a détecté cette capture d\'écran."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> et d\'autres applis ouvertes ont détecté cette capture d\'écran."</string> @@ -265,7 +266,7 @@ <string name="quick_settings_dnd_label" msgid="7728690179108024338">"Ne pas déranger"</string> <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string> <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Aucun appareil associé disponible."</string> - <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Appuyer pour connecter ou déconnecter un appareil"</string> + <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Appuyez pour connecter ou déconnecter un appareil"</string> <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Associer un nouvel appareil"</string> <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Tout afficher"</string> <string name="turn_on_bluetooth" msgid="5681370462180289071">"Utiliser le Bluetooth"</string> @@ -367,7 +368,7 @@ <string name="quick_settings_contrast_standard" msgid="2538227821968061832">"Standard"</string> <string name="quick_settings_contrast_medium" msgid="5158352575583902566">"Moyen"</string> <string name="quick_settings_contrast_high" msgid="656049259587494499">"Élevé"</string> - <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Prothèses auditives"</string> + <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Appareils auditifs"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Appareils auditifs"</string> <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Associer un nouvel appareil"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Cliquer pour associer un nouvel appareil"</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Réactiver les applis pro ?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Réactiver"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fermer les widgets sur l\'écran de verrouillage"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +629,17 @@ <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> <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\'UI du système"</string> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Mauvaise connexion satellite"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Bonne connexion satellite"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Connexion satellite disponible"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS par satellite"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil professionnel"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Divertissant pour certains, mais pas pour tous"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner vous propose de nouvelles manières d\'adapter et de personnaliser l\'interface utilisateur Android. Ces fonctionnalités expérimentales peuvent être modifiées, cesser de fonctionner ou disparaître dans les versions futures. À utiliser avec prudence."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ajouter un bloc"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Déplacer vers <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ajouter à la position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Emplacement non valide."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloc ajouté"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloc supprimé"</string> diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml index 4682c02b0871..8e2a0115b448 100644 --- a/packages/SystemUI/res/values-gl/strings.xml +++ b/packages/SystemUI/res/values-gl/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bordo esquerdo: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Bordo dereito: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Captura de pantalla gardada na aplicación <xliff:g id="APP">%1$s</xliff:g> do perfil de traballo"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Gardouse na aplicación <xliff:g id="APP">%1$s</xliff:g> do perfil privado"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> detectou esta captura de pantalla."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outras aplicacións abertas detectaron esta captura de pantalla."</string> @@ -369,7 +370,7 @@ <string name="quick_settings_contrast_high" msgid="656049259587494499">"Nivel alto"</string> <string name="quick_settings_hearing_devices_label" msgid="7277170419679404129">"Dispositivos auditivos"</string> <string name="quick_settings_hearing_devices_dialog_title" msgid="9004774017688484981">"Dispositivos auditivos"</string> - <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Vincular un dispositivo novo"</string> + <string name="quick_settings_pair_hearing_devices" msgid="5987105102207447322">"Vincular dispositivo novo"</string> <string name="accessibility_hearing_device_pair_new_device" msgid="8440082580186130090">"Fai clic para vincular un novo dispositivo"</string> <string name="hearing_devices_presets_error" msgid="350363093458408536">"Non se puido actualizar a configuración predeterminada"</string> <string name="sensor_privacy_start_use_mic_dialog_title" msgid="563796653825944944">"Queres desbloquear o micrófono do dispositivo?"</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Reactivar apps do traballo?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Reactivar"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Ocultar os widgets na pantalla de bloqueo"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -591,7 +596,7 @@ <string name="screen_pinning_exit" msgid="4553787518387346893">"Deixouse de fixar a aplicación"</string> <string name="stream_voice_call" msgid="7468348170702375660">"Chamada"</string> <string name="stream_system" msgid="7663148785370565134">"Sistema"</string> - <string name="stream_ring" msgid="7550670036738697526">"Son"</string> + <string name="stream_ring" msgid="7550670036738697526">"Ton"</string> <string name="stream_music" msgid="2188224742361847580">"Multimedia"</string> <string name="stream_alarm" msgid="16058075093011694">"Alarma"</string> <string name="stream_notification" msgid="7930294049046243939">"Notificación"</string> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, mala conexión"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, boa conexión"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexión dispoñible"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS por satélite"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de traballo"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Diversión só para algúns"</string> <string name="tuner_warning" msgid="1861736288458481650">"O configurador da IU do sistema ofréceche formas adicionais de modificar e personalizar a interface de usuario de Android. Estas funcións experimentais poden cambiar, interromperse ou desaparecer en futuras versións. Continúa con precaución."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Engadir tarxeta"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover a <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engadir á posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posición non válida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posición <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Engadiuse a tarxeta"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Quitouse a tarxeta"</string> diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml index 0e03bcf89500..c0f6be96115c 100644 --- a/packages/SystemUI/res/values-gu/strings.xml +++ b/packages/SystemUI/res/values-gu/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"સૅટલાઇટ સાથે કનેક્ટેડ છે"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"સૅટલાઇટ સાથે કનેક્ટેડ નથી"</string> + <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">"સિસ્ટમ UI ટ્યૂનર તમને Android વપરાશકર્તા ઇન્ટરફેસને ટ્વીક અને કસ્ટમાઇઝ કરવાની વધારાની રીતો આપે છે. ભાવિ રીલિઝેસમાં આ પ્રાયોગિક સુવિધાઓ બદલાઈ, ભંગ અથવા અદૃશ્ય થઈ શકે છે. સાવધાની સાથે આગળ વધો."</string> @@ -860,6 +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> + <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-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml index 21861434907c..4cf74b5f0a08 100644 --- a/packages/SystemUI/res/values-hi/strings.xml +++ b/packages/SystemUI/res/values-hi/strings.xml @@ -97,6 +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> + <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">"Files"</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> @@ -274,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> @@ -446,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> @@ -459,6 +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> + <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> @@ -556,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> @@ -623,10 +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> - <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> को चलाया जा रहा है"</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> <string name="system_ui_tuner" msgid="1471348823289954729">"सिस्टम यूज़र इंटरफ़ेस (यूआई) ट्यूनर"</string> <string name="status_bar" msgid="4357390266055077437">"स्टेटस बार"</string> <string name="demo_mode" msgid="263484519766901593">"सिस्टम यूज़र इंटरफ़ेस (यूआई) डेमो मोड"</string> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"सैटलाइट सिग्नल से कनेक्ट है"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"सैटलाइट सिग्नल से कनेक्ट नहीं है"</string> + <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> @@ -860,6 +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> + <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-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml index f5b9ca560ddc..b211c9437539 100644 --- a/packages/SystemUI/res/values-hr/strings.xml +++ b/packages/SystemUI/res/values-hr/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Lijevi rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Desni rub <xliff:g id="PERCENT">%1$d</xliff:g> posto"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Spremljeno u aplikaciju <xliff:g id="APP">%1$s</xliff:g> u poslovnom profilu"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Spremljeno u aplikaciju <xliff:g id="APP">%1$s</xliff:g> na privatnom profilu"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> otkrila je ovu snimku zaslona."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> i druge otvorene aplikacije otkrile su ovu snimku zaslona."</string> @@ -274,7 +275,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Spremljeno"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"prekini vezu"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktiviraj"</string> - <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski ponovo uključi sutra"</string> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatski uključi ponovno sutra"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Značajke kao što su brzo dijeljenje i Pronađi moj uređaj koriste Bluetooth"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth će se uključiti sutra ujutro"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Zajedničko slušanje"</string> @@ -368,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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Pokrenuti poslovne aplikacije?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Ponovno pokreni"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zatvaranje widgeta na zaključanom zaslonu"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -589,9 +594,9 @@ <string name="screen_pinning_negative" msgid="6882816864569211666">"Ne, hvala"</string> <string name="screen_pinning_start" msgid="7483998671383371313">"Aplikacija je prikvačena"</string> <string name="screen_pinning_exit" msgid="4553787518387346893">"Aplikacija je otkvačena"</string> - <string name="stream_voice_call" msgid="7468348170702375660">"Nazovi"</string> + <string name="stream_voice_call" msgid="7468348170702375660">"Poziv"</string> <string name="stream_system" msgid="7663148785370565134">"Sustav"</string> - <string name="stream_ring" msgid="7550670036738697526">"Zvoni"</string> + <string name="stream_ring" msgid="7550670036738697526">"Zvonjenje"</string> <string name="stream_music" msgid="2188224742361847580">"Mediji"</string> <string name="stream_alarm" msgid="16058075093011694">"Alarm"</string> <string name="stream_notification" msgid="7930294049046243939">"Obavijest"</string> @@ -624,10 +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> - <string name="media_output_label_title" msgid="872824698593182505">"Reproducira se – <xliff:g id="LABEL">%s</xliff:g>"</string> - <string name="media_output_title_without_playing" msgid="3825663683169305013">"Zvuk će se reproducirati"</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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, slaba veza"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra veza"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, veza je dostupna"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS putem satelita"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Poslovni profil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Zabava za neke, ali ne za sve"</string> <string name="tuner_warning" msgid="1861736288458481650">"Ugađanje korisničkog sučelja sustava pruža vam dodatne načine za prilagodbu korisničkog sučelja Androida. Te se eksperimentalne značajke mogu promijeniti, prekinuti ili nestati u budućim izdanjima. Nastavite uz oprez."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodavanje kartice"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premještanje u prostoriju <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodavanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Položaj nije važeći."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartica je dodana"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartica je uklonjena"</string> diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml index 32dcdfd5d2f4..e736537cc125 100644 --- a/packages/SystemUI/res/values-hu/strings.xml +++ b/packages/SystemUI/res/values-hu/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Bal oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Jobb oldali rész <xliff:g id="PERCENT">%1$d</xliff:g> százaléka"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Mentve a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazás munkaprofiljába"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Mentve a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazás privát profiljában"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fájlok"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> észlelte ezt a képernyőképet."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"A(z) <xliff:g id="APPNAME">%1$s</xliff:g> és más nyitott alkalmazások észlelték ezt a képernyőképet."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"A lezárási képernyőn lévő modulok bezárása"</string> <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> + <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> @@ -623,10 +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> + <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> @@ -654,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Műhold, gyenge kapcsolat"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Műhold, jó kapcsolat"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Műhold, van rendelkezésre álló kapcsolat"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Műholdhoz csatlakozik"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Nem csatlakozik műholdhoz"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Műholdas SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Munkaprofil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Egyeseknek tetszik, másoknak nem"</string> <string name="tuner_warning" msgid="1861736288458481650">"A Kezelőfelület-hangoló az Android felhasználói felületének szerkesztéséhez és testreszabásához nyújt további megoldásokat. Ezek a kísérleti funkciók változhatnak vagy megsérülhetnek a későbbi kiadásokban, illetve eltűnhetnek azokból. Körültekintően járjon el."</string> @@ -860,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Mozaik hozzáadása"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Áthelyezés ide: <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Hozzáadás a következő pozícióhoz: <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Érvénytelen pozíció."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. hely"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kártya hozzáadva"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kártya eltávolítva"</string> diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml index ec090dd87be1..6a905c598e00 100644 --- a/packages/SystemUI/res/values-hy/strings.xml +++ b/packages/SystemUI/res/values-hy/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Վերսկսե՞լ աշխ. հավելվածները"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Վերսկսել"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Փակել վիջեթները կողպէկրանին"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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">"Համակարգի ՕՄ-ի կարգավորիչ"</string> <string name="status_bar" msgid="4357390266055077437">"Կարգավիճակի գոտի"</string> <string name="demo_mode" msgid="263484519766901593">"Համակարգի միջերեսի ցուցադրական ռեժիմ"</string> @@ -655,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</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> @@ -863,6 +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> + <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-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml index 2e36d21c3349..23d83974493c 100644 --- a/packages/SystemUI/res/values-in/strings.xml +++ b/packages/SystemUI/res/values-in/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Batas kiri <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Batas kanan <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Disimpan di <xliff:g id="APP">%1$s</xliff:g> di profil kerja"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Disimpan di <xliff:g id="APP">%1$s</xliff:g> di profil pribadi"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> mendeteksi screenshot ini."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dan aplikasi terbuka lainnya mendeteksi screenshot ini."</string> @@ -446,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> @@ -459,6 +462,11 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Tutup widget di layar kunci"</string> <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Sesuaikan widget"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widget di layar 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_multi_user_switch_switcher" msgid="5330448341251092660">"Beralih pengguna"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu pulldown"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Semua aplikasi dan data dalam sesi ini akan dihapus."</string> @@ -623,10 +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> + <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> @@ -654,8 +669,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, koneksi buruk"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, koneksi baik"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, koneksi tersedia"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Terhubung ke satelit"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Tidak terhubung ke satelit"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via Satelit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil kerja"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Tidak semua orang menganggapnya baik"</string> <string name="tuner_warning" msgid="1861736288458481650">"Penyetel Antarmuka Pengguna Sistem memberikan cara tambahan untuk mengubah dan menyesuaikan antarmuka pengguna Android. Fitur eksperimental ini dapat berubah, rusak, atau menghilang dalam rilis di masa mendatang. Lanjutkan dengan hati-hati."</string> @@ -860,6 +874,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tambahkan kartu"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pindahkan ke <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan ke posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisi tidak valid."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisi <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kartu ditambahkan"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kartu dihapus"</string> diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml index 208790733db9..2c77eb1f0345 100644 --- a/packages/SystemUI/res/values-is/strings.xml +++ b/packages/SystemUI/res/values-is/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vinstri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Hægri mörk <xliff:g id="PERCENT">%1$d</xliff:g> prósent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Vistað á vinnusniði í <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Vistað í <xliff:g id="APP">%1$s</xliff:g> á lokaða prófílnum"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skrár"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> greindi skjámyndina."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> og önnur opin forrit greindu skjámyndina."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Ljúka hléi vinnuforrita?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Ljúka hléi"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Loka græjum á lásskjá"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Gervihnöttur, léleg tenging"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Gervihnöttur, góð tenging"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Gervihnöttur, tenging tiltæk"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Gervihnattar-SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Vinnusnið"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Þetta er ekki allra"</string> <string name="tuner_warning" msgid="1861736288458481650">"Fínstillingar kerfisviðmóts gera þér kleift að fínstilla og sérsníða notendaviðmót Android. Þessir tilraunaeiginleikar geta breyst, bilað eða horfið í síðari útgáfum. Gakktu því hægt um gleðinnar dyr."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Bæta flís við"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Færa í <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bæta við í stöðu <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Staða ógild."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Staða <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Reit bætt við"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Reitur fjarlægður"</string> diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml index 5931e6af2aad..056c2867388a 100644 --- a/packages/SystemUI/res/values-it/strings.xml +++ b/packages/SystemUI/res/values-it/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite sinistro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite destro, <xliff:g id="PERCENT">%1$d</xliff:g> percento"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvato nell\'app <xliff:g id="APP">%1$s</xliff:g> nel profilo di lavoro"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salvato nel profilo privato nell\'app <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"File"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> ha rilevato questo screenshot."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e altre app aperte hanno rilevato questo screenshot."</string> @@ -446,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> @@ -457,9 +460,13 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Riattivare le app di lavoro?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Riattiva"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Chiudi widget su schermata di blocco"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Personalizza widget"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widget su schermata di blocco"</string> + <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"seleziona 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_multi_user_switch_switcher" msgid="5330448341251092660">"Cambio utente"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"menu a discesa"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Tutte le app e i dati di questa sessione verranno eliminati."</string> @@ -624,10 +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> + <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> @@ -655,8 +669,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellitare, connessione debole"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellitare, connessione buona"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellitare, connessione disponibile"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Connessione satellitare"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Nessuna connessione satellitare"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS satellitare"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profilo di lavoro"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Il divertimento riservato a pochi eletti"</string> <string name="tuner_warning" msgid="1861736288458481650">"L\'Ottimizzatore UI di sistema mette a disposizione altri metodi per modificare e personalizzare l\'interfaccia utente di Android. Queste funzioni sperimentali potrebbero cambiare, interrompersi o scomparire nelle versioni successive. Procedi con cautela."</string> @@ -861,6 +874,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Aggiungi riquadro"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Sposta nella posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Aggiungi alla posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posizione non valida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posizione <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Riquadro aggiunto"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Riquadro rimosso"</string> diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml index 8c2ccbfd44d3..6670b097ac53 100644 --- a/packages/SystemUI/res/values-iw/strings.xml +++ b/packages/SystemUI/res/values-iw/strings.xml @@ -97,6 +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> + <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> @@ -268,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> @@ -446,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> @@ -457,9 +460,13 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"להפעיל את האפליקציות לעבודה?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"ביטול ההשהיה"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"סגירת ווידג\'טים במסך הנעילה"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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_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> @@ -624,10 +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> + <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> @@ -655,8 +669,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"יש חיבור ללוויין"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"אין חיבור ללוויין"</string> + <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">"התכונה System UI Tuner מספקת לך דרכים נוספות להתאים אישית את ממשק המשתמש של Android. התכונות הניסיוניות האלה עשויות להשתנות, לא לעבוד כראוי או להיעלם בגרסאות עתידיות. יש להמשיך בזהירות."</string> @@ -861,6 +874,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> + <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-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml index 54f9c86519a1..e3c0dbcf9992 100644 --- a/packages/SystemUI/res/values-ja/strings.xml +++ b/packages/SystemUI/res/values-ja/strings.xml @@ -97,6 +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> + <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> @@ -271,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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"衛星に接続済み"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"衛星に未接続"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"衛星 SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"仕事用プロファイル"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"一部の方のみお楽しみいただける限定公開ツール"</string> <string name="tuner_warning" msgid="1861736288458481650">"システムUI調整ツールでは、Androidユーザーインターフェースの調整やカスタマイズを行えます。これらの試験運用機能は今後のリリースで変更となったり、中止となったり、削除されたりする可能性がありますのでご注意ください。"</string> @@ -860,6 +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> + <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-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml index 9c814b42a8c7..0251ac0e9d7c 100644 --- a/packages/SystemUI/res/values-ka/strings.xml +++ b/packages/SystemUI/res/values-ka/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"სატელიტური SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"სამსახურის პროფილი"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"ზოგისთვის გასართობია, მაგრამ არა ყველასთვის"</string> <string name="tuner_warning" msgid="1861736288458481650">"სისტემის UI ტუნერი გაძლევთ დამატებით გზებს Android-ის სამომხმარებლო ინტერფეისის პარამეტრების დაყენებისთვის. ეს ექსპერიმენტული მახასიათებლები შეიძლება შეიცვალოს, შეწყდეს ან გაქრეს მომავალ ვერსიებში. სიფრთხილით გააგრძელეთ."</string> @@ -862,6 +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> + <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-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml index 5fe1a2c750a3..9c96a4eb6b60 100644 --- a/packages/SystemUI/res/values-kk/strings.xml +++ b/packages/SystemUI/res/values-kk/strings.xml @@ -97,6 +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> + <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">"Files"</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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Жерсерік сигналына қосылғансыз."</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Жерсерік сигналына қосылмағансыз."</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</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> @@ -860,6 +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> + <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-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml index 91292a10b4f0..dc9a747a70b4 100644 --- a/packages/SystemUI/res/values-km/strings.xml +++ b/packages/SystemUI/res/values-km/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"បានភ្ជាប់ទៅផ្កាយរណប"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"មិនបានភ្ជាប់ទៅផ្កាយរណប"</string> + <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">"កម្មវិធីសម្រួល UI ប្រព័ន្ធផ្តល់ជូនអ្នកនូវមធ្យោបាយបន្ថែមទៀតដើម្បីកែសម្រួល និងប្តូរចំណុចប្រទាក់អ្នកប្រើ Android តាមបំណង។ លក្ខណៈពិសេសសាកល្បងនេះអាចនឹងផ្លាស់ប្តូរ បំបែក ឬបាត់បង់បន្ទាប់ពីការចេញផ្សាយនាពេលអនាគត។ សូមបន្តដោយប្រុងប្រយ័ត្ន។"</string> @@ -860,6 +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> + <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-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml index 0d4449aaf955..46a679cff801 100644 --- a/packages/SystemUI/res/values-kn/strings.xml +++ b/packages/SystemUI/res/values-kn/strings.xml @@ -97,6 +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> + <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> @@ -367,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> @@ -446,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> @@ -459,6 +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> + <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> @@ -588,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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"ಸ್ಯಾಟಲೈಟ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"ಸ್ಯಾಟಲೈಟ್ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಿಲ್ಲ"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ಸ್ಯಾಟಲೈಟ್ SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"ಕೆಲವರಿಗೆ ಮೋಜು ಆಗಿದೆ ಎಲ್ಲರಿಗೆ ಇಲ್ಲ"</string> <string name="tuner_warning" msgid="1861736288458481650">"ಸಿಸ್ಟಂ UI ಟ್ಯೂನರ್ ನಿಮಗೆ Android ಬಳಕೆದಾರ ಅಂತರಸಂಪರ್ಕವನ್ನು ಸರಿಪಡಿಸಲು ಮತ್ತು ಕಸ್ಟಮೈಸ್ ಮಾಡಲು ಹೆಚ್ಚುವರಿ ಮಾರ್ಗಗಳನ್ನು ನೀಡುತ್ತದೆ. ಈ ಪ್ರಾಯೋಗಿಕ ವೈಶಿಷ್ಟ್ಯಗಳು ಭವಿಷ್ಯದ ಬಿಡುಗಡೆಗಳಲ್ಲಿ ಬದಲಾಗಬಹುದು, ವಿರಾಮವಾಗಬಹುದು ಅಥವಾ ಕಾಣಿಸಿಕೊಳ್ಳದಿರಬಹುದು. ಎಚ್ಚರಿಕೆಯಿಂದ ಮುಂದುವರಿಯಿರಿ."</string> @@ -860,6 +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> + <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-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml index 2d9058eb6b88..54dcfce6d5b8 100644 --- a/packages/SystemUI/res/values-ko/strings.xml +++ b/packages/SystemUI/res/values-ko/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"직장 앱 일시중지를 해제하시겠습니까?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"일시중지 해제"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"잠금 화면에서 위젯 닫기"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"위성 긴급 SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"직장 프로필"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"마음에 들지 않을 수도 있음"</string> <string name="tuner_warning" msgid="1861736288458481650">"시스템 UI 튜너를 사용하면 Android 사용자 인터페이스를 변경 및 맞춤설정할 수 있습니다. 이러한 실험실 기능은 향후 출시 버전에서는 변경되거나 다운되거나 사라질 수 있습니다. 신중하게 진행하시기 바랍니다."</string> @@ -863,6 +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> + <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-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml index 1767c07613a5..a224ac78d599 100644 --- a/packages/SystemUI/res/values-ky/strings.xml +++ b/packages/SystemUI/res/values-ky/strings.xml @@ -97,6 +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> + <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">"Files"</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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Жумуш колдонмолорун иштетесизби?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Иштетүү"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Виджеттерди кулпуланган экранда жабуу"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Спутник SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Жумуш профили"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Баарына эле жага бербейт"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner Android колдонуучу интерфейсин жөнгө салып жана ыңгайлаштыруунун кошумча ыкмаларын сунуштайт. Бул сынамык функциялар кийинки чыгарылыштарда өзгөрүлүп, бузулуп же жоголуп кетиши мүмкүн. Абайлап колдонуңуз."</string> @@ -863,6 +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> + <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-land/styles.xml b/packages/SystemUI/res/values-land/styles.xml index 2769bea141c9..73812c965a17 100644 --- a/packages/SystemUI/res/values-land/styles.xml +++ b/packages/SystemUI/res/values-land/styles.xml @@ -39,7 +39,7 @@ </style> <style name="TextAppearance.AuthNonBioCredential.Title"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">6dp</item> <item name="android:textSize">36dp</item> <item name="android:focusable">true</item> @@ -47,14 +47,14 @@ </style> <style name="TextAppearance.AuthNonBioCredential.Subtitle"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">6dp</item> <item name="android:textSize">18sp</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> </style> <style name="TextAppearance.AuthNonBioCredential.Description"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">6dp</item> <item name="android:textSize">18sp</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml index dc596bf20708..88866e37d07a 100644 --- a/packages/SystemUI/res/values-lo/strings.xml +++ b/packages/SystemUI/res/values-lo/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> <string name="demo_mode" msgid="263484519766901593">"ໂໝດເດໂມສ່ວນຕິດຕໍ່ຜູ້ໃຊ້ລະບົບ"</string> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"ເຊື່ອມຕໍ່ຫາດາວທຽມແລ້ວ"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"ບໍ່ໄດ້ເຊື່ອມຕໍ່ຫາດາວທຽມ"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS ດາວທຽມ"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"ມ່ວນຊື່ນສຳລັບບາງຄົນ ແຕ່ບໍ່ແມ່ນສຳລັບທຸກຄົນ"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner ໃຫ້ທ່ານມີວິທີພິເສດຕື່ມອີກໃນການປັບປ່ຽນ ແລະຕົບແຕ່ງສ່ວນຕໍ່ປະສານຜູ້ໃຊ້ຂອງ Android. ຄຸນສົມບັດທົດລອງໃຊ້ເຫຼົ່ານີ້ອາດຈະປ່ຽນແປງ, ຢຸດເຊົາ ຫຼືຫາຍໄປໃນການວາງຈຳໜ່າຍໃນອະນາຄົດ. ຈົ່ງດຳເນີນຕໍ່ດ້ວຍຄວາມລະມັດລະວັງ."</string> @@ -860,6 +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> + <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-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml index e6703ce49737..46ac3044f765 100644 --- a/packages/SystemUI/res/values-lt/strings.xml +++ b/packages/SystemUI/res/values-lt/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kairioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Dešinioji riba – <xliff:g id="PERCENT">%1$d</xliff:g> proc."</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Išsaugota programoje „<xliff:g id="APP">%1$s</xliff:g>“ darbo profilyje"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Išsaugota „<xliff:g id="APP">%1$s</xliff:g>“ privačiame profilyje"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Failai"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ aptiko šią ekrano kopiją."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"„<xliff:g id="APPNAME">%1$s</xliff:g>“ ir kitos atidarytos programos aptiko šią ekrano kopiją."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Uždaryti valdiklius užrakinimo ekrane"</string> <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> + <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> @@ -623,10 +629,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"Sistemos naudotojo sąsajos derinimo priemonė"</string> <string name="status_bar" msgid="4357390266055077437">"Būsenos juosta"</string> <string name="demo_mode" msgid="263484519766901593">"Sistemos NS demonstracinis režimas"</string> @@ -654,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Palydovas, prastas ryšys"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Palydovas, geras ryšys"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Palydovas, pasiekiamas ryšys"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Prisijungta prie palydovo"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Neprisijungta prie palydovo"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Prisijungimas prie palydovo kritiniu atveju"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Darbo profilis"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Smagu, bet ne visada"</string> <string name="tuner_warning" msgid="1861736288458481650">"Sistemos naudotojo sąsajos derinimo priemonė suteikia papildomų galimybių pagerinti ir tinkinti „Android“ naudotojo sąsają. Šios eksperimentinės funkcijos gali pasikeisti, nutrūkti ar išnykti iš būsimų laidų. Tęskite atsargiai."</string> @@ -860,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pridėti išklotinės elementą"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Perkelkite į <xliff:g id="POSITION">%1$d</xliff:g> poziciją"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridėkite <xliff:g id="POSITION">%1$d</xliff:g> pozicijoje"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Padėtis netinkama."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g> pozicija"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Išklotinė pridėta"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Išklotinė pašalinta"</string> diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml index 9a2a29230a95..979f59e3374f 100644 --- a/packages/SystemUI/res/values-lv/strings.xml +++ b/packages/SystemUI/res/values-lv/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kreisā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Labā mala: <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Saglabāts lietotnē <xliff:g id="APP">%1$s</xliff:g> darba profilā"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Saglabāts privātā profilā lietotnē <xliff:g id="APP">%1$s</xliff:g>"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> konstatēja, ka tika veikts ekrānuzņēmums."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> un citas atvērtas lietotnes konstatēja, ka tika veikts ekrānuzņēmums."</string> @@ -369,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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Vai aktivizēt darba lietotnes?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Aktivizēt"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Aizvērt logrīkus bloķēšanas ekrānā"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelīts, vājš savienojums"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelīts, labs savienojums"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelīts, ir pieejams savienojums"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelīta SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Darba profils"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Jautri dažiem, bet ne visiem"</string> <string name="tuner_warning" msgid="1861736288458481650">"Sistēmas saskarnes regulators sniedz papildu veidus, kā mainīt un pielāgot Android lietotāja saskarni. Nākamajās versijās šīs eksperimentālās funkcijas var tikt mainītas, bojātas vai to darbība var tikt pārtraukta. Turpinot esiet uzmanīgs."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pievienot elementu"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Pārvietot uz pozīciju numur <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pievienot elementu pozīcijā numur <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nederīga pozīcija."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozīcija numur <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Elements ir pievienots"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Elements ir noņemts"</string> diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml index 552e5e501582..e305c9e5fee1 100644 --- a/packages/SystemUI/res/values-mk/strings.xml +++ b/packages/SystemUI/res/values-mk/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,13 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Да се актив. работните аплик.?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Прекини ја паузата"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Затворете ги виџетите на заклучениот екран"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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_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> @@ -591,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> @@ -624,10 +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> + <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> @@ -655,10 +669,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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Сателитски SOS"</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> @@ -863,6 +874,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> + <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-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml index c3fdf846a603..c14cc418d5a8 100644 --- a/packages/SystemUI/res/values-ml/strings.xml +++ b/packages/SystemUI/res/values-ml/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"സാറ്റലൈറ്റുമായി കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"സാറ്റലൈറ്റുമായി കണക്റ്റ് ചെയ്തിട്ടില്ല"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"സാറ്റലൈറ്റ് SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"ചിലർക്ക് വിനോദം, എന്നാൽ എല്ലാവർക്കുമില്ല"</string> <string name="tuner_warning" msgid="1861736288458481650">"Android ഉപയോക്തൃ ഇന്റർഫേസ് ആവശ്യമുള്ള രീതിയിൽ മാറ്റുന്നതിനും ഇഷ്ടാനുസൃതമാക്കുന്നതിനും സിസ്റ്റം UI ട്യൂണർ നിങ്ങൾക്ക് അധിക വഴികൾ നൽകുന്നു. ഭാവി റിലീസുകളിൽ ഈ പരീക്ഷണാത്മക ഫീച്ചറുകൾ മാറ്റുകയോ നിർത്തുകയോ അപ്രത്യക്ഷമാവുകയോ ചെയ്തേക്കാം. ശ്രദ്ധയോടെ മുന്നോട്ടുപോകുക."</string> @@ -860,6 +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> + <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-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml index 428ceffc5e6f..573c8ff7e7d6 100644 --- a/packages/SystemUI/res/values-mn/strings.xml +++ b/packages/SystemUI/res/values-mn/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Ажлын аппыг үргэлжлүүлэх үү?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Үргэлжлүүлэх"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Түгжээтэй дэлгэц дээр виджетүүдийг хаах"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> - <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> дээр тоглуулж байна"</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> <string name="system_ui_tuner" msgid="1471348823289954729">"Системийн UI Тохируулагч"</string> <string name="status_bar" msgid="4357390266055077437">"Статус самбар"</string> <string name="demo_mode" msgid="263484519766901593">"Системийн UI демо горим"</string> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Хиймэл дагуулд холбогдсон"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Хиймэл дагуулд холбогдоогүй"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Хиймэл дагуул SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Ажлын профайл"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Зарим хүнд хөгжилтэй байж болох ч бүх хүнд тийм биш"</string> <string name="tuner_warning" msgid="1861736288458481650">"Системийн UI Tохируулагч нь Android хэрэглэгчийн интерфэйсийг тааруулах, өөрчлөх нэмэлт аргыг зааж өгөх болно. Эдгээр туршилтын тохиргоо нь цаашид өөрчлөгдөх, эвдрэх, алга болох магадлалтай. Үйлдлийг болгоомжтой хийнэ үү."</string> @@ -861,6 +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> + <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-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml index f5978761652a..3667f1e9dcd1 100644 --- a/packages/SystemUI/res/values-mr/strings.xml +++ b/packages/SystemUI/res/values-mr/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"उपग्रहाशी जोडलेले आहे"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"उपग्रहाशी जोडलेले नाही"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"सॅटेलाइट SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"कार्य प्रोफाईल"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"सर्वांसाठी नाही तर काहींसाठी मजेदार असू शकते"</string> <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम UI ट्युनर आपल्याला Android यूझर इंटरफेस ट्विक आणि कस्टमाइझ करण्याचे अनेक प्रकार देते. ही प्रयोगात्मक वैशिष्ट्ये बदलू शकतात, खंडित होऊ शकतात किंवा भविष्यातील रिलीज मध्ये कदाचित दिसणार नाहीत. सावधगिरी बाळगून पुढे सुरू ठेवा."</string> @@ -860,6 +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> + <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-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml index 9e3117d8867d..60aedfa449cf 100644 --- a/packages/SystemUI/res/values-ms/strings.xml +++ b/packages/SystemUI/res/values-ms/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sempadan kiri <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sempadan kanan <xliff:g id="PERCENT">%1$d</xliff:g> peratus"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Disimpan dalam <xliff:g id="APP">%1$s</xliff:g> dalam profil kerja"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Disimpan dalam <xliff:g id="APP">%1$s</xliff:g> pada profil peribadi"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fail"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> telah mengesan tangkapan skrin ini."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dan apl lain yang dibuka telah mengesan tangkapan skrin ini."</string> @@ -235,7 +236,7 @@ <string name="accessibility_desc_notification_shade" msgid="5355229129428759989">"Bidai pemberitahuan."</string> <string name="accessibility_desc_quick_settings" msgid="4374766941484719179">"Tetapan pantas."</string> <string name="accessibility_desc_qs_notification_shade" msgid="8327226953072700376">"Tetapan pantas dan Bidai pemberitahuan."</string> - <string name="accessibility_desc_lock_screen" msgid="409034672704273634">"Kunci skrin"</string> + <string name="accessibility_desc_lock_screen" msgid="409034672704273634">"Skrin kunci"</string> <string name="accessibility_desc_work_lock" msgid="4355620395354680575">"Skrin kunci kerja"</string> <string name="accessibility_desc_close" msgid="8293708213442107755">"Tutup"</string> <string name="accessibility_quick_settings_dnd_none_on" msgid="3235552940146035383">"senyap sepenuhnya"</string> @@ -274,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> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Tutup widget pada skrin kunci"</string> <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> + <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> @@ -623,10 +629,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"Penala UI Sistem"</string> <string name="status_bar" msgid="4357390266055077437">"Bar status"</string> <string name="demo_mode" msgid="263484519766901593">"Mod tunjuk cara UI sistem"</string> @@ -654,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, sambungan yang lemah"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, sambungan yang baik"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, sambungan tersedia"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Disambungkan kepada satelit"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Tidak disambungkan kepada satelit"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via Satelit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil kerja"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Menarik untuk sesetengah orang tetapi bukan untuk semua"</string> <string name="tuner_warning" msgid="1861736288458481650">"Penala UI Sistem memberi anda cara tambahan untuk mengolah dan menyesuaikan antara muka Android. Ciri eksperimen ini boleh berubah, rosak atau hilang dalam keluaran masa hadapan. Teruskan dengan berhati-hati."</string> @@ -860,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tambahkan jubin"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Alih ke <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Tambahkan pada kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Kedudukan tidak sah."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Kedudukan <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Jubin ditambah"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Jubin dialih keluar"</string> diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml index 86345a80bff1..b7398d0efa13 100644 --- a/packages/SystemUI/res/values-my/strings.xml +++ b/packages/SystemUI/res/values-my/strings.xml @@ -97,6 +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> + <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> @@ -274,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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"အလုပ်သုံးအက်ပ် ပြန်ဖွင့်မလား။"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"ပြန်ဖွင့်ရန်"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"လော့ခ်မျက်နှာပြင်ရှိ ဝိဂျက်များကို ပိတ်ရန်"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -614,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> @@ -624,10 +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> - <string name="media_output_label_title" msgid="872824698593182505">"<xliff:g id="LABEL">%s</xliff:g> ကို ဖွင့်နေသည်"</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> <string name="system_ui_tuner" msgid="1471348823289954729">"စနစ် UI ဖမ်းစက်"</string> <string name="status_bar" msgid="4357390266055077437">"အခြေအနေပြနေရာ"</string> <string name="demo_mode" msgid="263484519766901593">"စနစ် UI စရုပ်ပြမုဒ်"</string> @@ -655,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"အလုပ် ပရိုဖိုင်"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"အချို့သူများ အတွက် ပျော်စရာ ဖြစ်ပေမဲ့ အားလုံး အတွက် မဟုတ်ပါ"</string> <string name="tuner_warning" msgid="1861736288458481650">"စနစ် UI Tuner က သင့်အတွက် Android အသုံးပြုသူ အင်တာဖေ့စ်ကို ပြောင်းရန်နှင့် စိတ်ကြိုက်ပြုလုပ်ရန် နည်းလမ်း အပိုများကို သင့်အတွက် စီစဉ်ပေးသည်။ အနာဂတ်ဗားရှင်းများတွင် ဤစမ်းသပ်အင်္ဂါရပ်များမှာ ပြောင်းလဲ၊ ပျက်စီး သို့မဟုတ် ပျောက်ကွယ်သွားနိုင်သည်။ သတိဖြင့် ရှေ့ဆက်ပါ။"</string> @@ -863,6 +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> + <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-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml index 4c68ddad1ea4..2b131c7e5407 100644 --- a/packages/SystemUI/res/values-nb/strings.xml +++ b/packages/SystemUI/res/values-nb/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Venstre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Høyre grense <xliff:g id="PERCENT">%1$d</xliff:g> prosent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Lagret i <xliff:g id="APP">%1$s</xliff:g> i jobbprofilen"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Lagret i <xliff:g id="APP">%1$s</xliff:g> i den private profilen"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> har registrert denne skjermdumpen."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> og andre åpne apper har registrert denne skjermdumpen."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Vil du slå på jobbapper igjen?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Slå på"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Lukk moduler på låseskjermen"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellitt – dårlig tilkobling"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellitt – god tilkobling"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellitt – tilkobling tilgjengelig"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-alarm via satellitt"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Work-profil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Gøy for noen – ikke for alle"</string> <string name="tuner_warning" msgid="1861736288458481650">"Med System UI Tuner har du flere måter å justere og tilpasse Android-brukergrensesnittet på. Disse eksperimentelle funksjonene kan endres, avbrytes eller fjernes i fremtidige utgivelser. Fortsett med forbehold."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Legg til en infobrikke"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flytt til <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Legg til posisjonen <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posisjonen er ugyldig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisjon <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"En infobrikke er lagt til"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"En infobrikke er fjernet"</string> diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml index 400fed07cde6..42abd4439421 100644 --- a/packages/SystemUI/res/values-ne/strings.xml +++ b/packages/SystemUI/res/values-ne/strings.xml @@ -97,6 +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> + <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">"Files"</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> @@ -174,7 +175,7 @@ <string name="biometric_dialog_wrong_pattern" msgid="8954812279840889029">"प्याटर्न मिलेन"</string> <string name="biometric_dialog_wrong_password" msgid="69477929306843790">"पासवर्ड मिलेन"</string> <string name="biometric_dialog_credential_too_many_attempts" msgid="3083141271737748716">"अत्यन्तै धेरै पटक गलत प्रयास गरिए। \n <xliff:g id="NUMBER">%d</xliff:g>सेकेन्ड पछि पुनः प्रयास गर्नुहोस्।"</string> - <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"आपत्कालीन"</string> + <string name="work_challenge_emergency_button_text" msgid="8946588434515599288">"आपत्कालीन"</string> <string name="biometric_dialog_credential_attempts_before_wipe" msgid="6751859711975516999">"फेरि प्रयास गर्नुहोस्। <xliff:g id="MAX_ATTEMPTS">%2$d</xliff:g> मध्ये <xliff:g id="ATTEMPTS_0">%1$d</xliff:g> प्रयास।"</string> <string name="biometric_dialog_last_attempt_before_wipe_dialog_title" msgid="2874250099278693477">"तपाईंको डेटा मेटाइने छ"</string> <string name="biometric_dialog_last_pattern_attempt_before_wipe_device" msgid="6562299244825817598">"तपाईंले अर्को पटक पनि गलत ढाँचा प्रविष्टि गर्नुभयो भने यो डिभाइसको डेटा मेटाइने छ।"</string> @@ -202,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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"स्याटलाइट SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"कार्य प्रोफाइल"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"केहीका लागि रमाइलो हुन्छ तर सबैका लागि होइन"</string> <string name="tuner_warning" msgid="1861736288458481650">"सिस्टम UI ट्युनरले तपाईँलाई Android प्रयोगकर्ता इन्टरफेस आफू अनुकूल गर्न र ट्विक गर्न थप तरिकाहरू प्रदान गर्छ। यी प्रयोगात्मक सुविधाहरू भावी विमोचनमा परिवर्तन हुन, बिग्रिन वा हराउन सक्ने छन्। सावधानीपूर्वक अगाडि बढ्नुहोस्।"</string> @@ -862,6 +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> + <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-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml index 8dcfe9c5dee7..c459b93e87cb 100644 --- a/packages/SystemUI/res/values-nl/strings.xml +++ b/packages/SystemUI/res/values-nl/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Linkergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Rechtergrens <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Opgeslagen in <xliff:g id="APP">%1$s</xliff:g> in het werkprofiel."</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Opgeslagen in <xliff:g id="APP">%1$s</xliff:g> in het privéprofiel"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Bestanden"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> heeft dit screenshot waargenomen."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> en andere geopende apps hebben dit screenshot waargenomen."</string> @@ -446,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> @@ -459,6 +462,11 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Widgets op het vergrendelscherm sluiten"</string> <string name="accessibility_action_label_edit_widgets" msgid="3821868581348322346">"Widgets aanpassen"</string> <string name="accessibility_content_description_for_communal_hub" msgid="1670220840599380118">"Widgets op het vergrendelscherm"</string> + <string name="accessibility_action_label_select_widget" msgid="8897281501387398191">"widget selecteren"</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_multi_user_switch_switcher" msgid="5330448341251092660">"Gebruiker wijzigen"</string> <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"pull-downmenu"</string> <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"Alle apps en gegevens in deze sessie worden verwijderd."</string> @@ -590,14 +598,14 @@ <string name="screen_pinning_exit" msgid="4553787518387346893">"App losgemaakt"</string> <string name="stream_voice_call" msgid="7468348170702375660">"Bellen"</string> <string name="stream_system" msgid="7663148785370565134">"Systeem"</string> - <string name="stream_ring" msgid="7550670036738697526">"Bellen"</string> + <string name="stream_ring" msgid="7550670036738697526">"Beltoon"</string> <string name="stream_music" msgid="2188224742361847580">"Media"</string> <string name="stream_alarm" msgid="16058075093011694">"Wekker"</string> <string name="stream_notification" msgid="7930294049046243939">"Melding"</string> <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string> <string name="stream_dtmf" msgid="7322536356554673067">"Frequentie voor tweevoudige multitoon"</string> <string name="stream_accessibility" msgid="3873610336741987152">"Toegankelijkheid"</string> - <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Bellen"</string> + <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Beltoon"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Trillen"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Geluid staat uit"</string> <string name="media_device_cast" msgid="4786241789687569892">"Casten"</string> @@ -613,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> @@ -623,10 +631,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"Systeem-UI-tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Statusbalk"</string> <string name="demo_mode" msgid="263484519766901593">"Demomodus voor systeemgebruikersinterface"</string> @@ -654,8 +669,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelliet, slechte verbinding"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelliet, goede verbinding"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelliet, verbinding beschikbaar"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Verbonden met satelliet"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Niet verbonden met satelliet"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satelliet"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Werkprofiel"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Leuk voor sommige gebruikers, maar niet voor iedereen"</string> <string name="tuner_warning" msgid="1861736288458481650">"Met Systeem-UI-tuner beschikt u over extra manieren om de Android-gebruikersinterface aan te passen. Deze experimentele functies kunnen veranderen, vastlopen of verdwijnen in toekomstige releases. Ga voorzichtig verder."</string> @@ -860,6 +874,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Tegel toevoegen"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Verplaatsen naar <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Toevoegen aan positie <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positie ongeldig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Positie <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Tegel toegevoegd"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Tegel verwijderd"</string> @@ -1103,7 +1118,7 @@ <string name="media_output_dialog_multiple_devices" msgid="1093771040315422350">"<xliff:g id="COUNT">%1$d</xliff:g> apparaten geselecteerd"</string> <string name="media_output_dialog_disconnected" msgid="7090512852817111185">"(verbinding verbroken)"</string> <string name="media_output_dialog_connect_failed" msgid="3080972621975339387">"Kan niet schakelen. Tik om het opnieuw te proberen."</string> - <string name="media_output_dialog_pairing_new" msgid="5098212763195577270">"Een apparaat koppelen"</string> + <string name="media_output_dialog_pairing_new" msgid="5098212763195577270">"Apparaat koppelen"</string> <string name="media_output_dialog_launch_app_text" msgid="1527413319632586259">"Als je deze sessie wilt casten, open je de app."</string> <string name="media_output_dialog_unknown_launch_app_name" msgid="1084899329829371336">"Onbekende app"</string> <string name="media_output_dialog_button_stop_casting" msgid="6581379537930199189">"Casten stoppen"</string> diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml index e164c3df7acb..1444e6d69bc3 100644 --- a/packages/SystemUI/res/values-or/strings.xml +++ b/packages/SystemUI/res/values-or/strings.xml @@ -97,6 +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> + <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> @@ -369,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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"ୱାର୍କ ଆପ୍ସକୁ ପୁଣି ଚାଲୁ କରିବେ?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"ପୁଣି ଚାଲୁ କରନ୍ତୁ"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ଲକ ସ୍କ୍ରିନରେ ଥିବା ୱିଜେଟଗୁଡ଼ିକୁ ବନ୍ଦ କରନ୍ତୁ"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"ସେଟେଲାଇଟ ସହ କନେକ୍ଟ ହୋଇଛନ୍ତି"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"ସେଟେଲାଇଟ ସହ କନେକ୍ଟ ହୋଇନାହାଁନ୍ତି"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ସେଟେଲାଇଟ SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"ୱର୍କ ପ୍ରୋଫାଇଲ୍"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"କେତେକଙ୍କ ପାଇଁ ମଜାଦାର, କିନ୍ତୁ ସମସ୍ତଙ୍କ ପାଇଁ ନୁହେଁ"</string> <string name="tuner_warning" msgid="1861736288458481650">"Android ୟୁଜର୍ ଇଣ୍ଟରଫେସ୍ ବଦଳାଇବାକୁ ତଥା ନିଜ ପସନ୍ଦ ଅନୁଯାୟୀ କରିବାକୁ ସିଷ୍ଟମ୍ UI ଟ୍ୟୁନର୍ ଆପଣଙ୍କୁ ଅତିରିକ୍ତ ଉପାୟ ପ୍ରଦାନ କରେ। ଏହି ପରୀକ୍ଷାମୂଳକ ସୁବିଧାମାନ ବଦଳିପାରେ, ଭାଙ୍ଗିପାରେ କିମ୍ବା ଭବିଷ୍ୟତର ରିଲିଜ୍ଗୁଡ଼ିକରେ ନଦେଖାଯାଇପାରେ। ସତର୍କତାର ସହ ଆଗକୁ ବଢ଼ନ୍ତୁ।"</string> @@ -861,6 +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> + <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-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml index 4e43e1708655..23914b3f16c6 100644 --- a/packages/SystemUI/res/values-pa/strings.xml +++ b/packages/SystemUI/res/values-pa/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਤੋਂ ਰੋਕ ਹਟਾਈਏ?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"ਰੋਕ ਹਟਾਓ"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ਲਾਕ ਸਕ੍ਰੀਨ \'ਤੇ ਵਿਜੇਟ ਬੰਦ ਕਰੋ"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"ਸੈਟੇਲਾਈਟ ਨਾਲ ਕਨੈਕਟ ਹੈ"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"ਸੈਟੇਲਾਈਟ ਨਾਲ ਕਨੈਕਟ ਨਹੀਂ ਹੈ"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"ਸੈਟੇਲਾਈਟ SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"ਕੁਝ ਵਾਸਤੇ ਤਾਂ ਮਜ਼ੇਦਾਰ ਹੈ ਲੇਕਿਨ ਸਾਰਿਆਂ ਵਾਸਤੇ ਨਹੀਂ"</string> <string name="tuner_warning" msgid="1861736288458481650">"ਸਿਸਟਮ UI ਟਿਊਨਰ ਤੁਹਾਨੂੰ Android ਵਰਤੋਂਕਾਰ ਇੰਟਰਫ਼ੇਸ ਤਬਦੀਲ ਕਰਨ ਅਤੇ ਵਿਉਂਤਬੱਧ ਕਰਨ ਲਈ ਵਾਧੂ ਤਰੀਕੇ ਦਿੰਦਾ ਹੈ। ਇਹ ਪ੍ਰਯੋਗਾਤਮਿਕ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਭਵਿੱਖ ਦੀ ਰੀਲੀਜ਼ ਵਿੱਚ ਬਦਲ ਸਕਦੀਆਂ ਹਨ, ਟੁੱਟ ਸਕਦੀਆਂ ਹਨ, ਜਾਂ ਅਲੋਪ ਹੋ ਸਕਦੀਆਂ ਹਨ। ਸਾਵਧਾਨੀ ਨਾਲ ਅੱਗੇ ਵੱਧੋ।"</string> @@ -861,6 +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> + <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-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml index f57fe29e8f2b..4609f08ca0e0 100644 --- a/packages/SystemUI/res/values-pl/strings.xml +++ b/packages/SystemUI/res/values-pl/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Przycięcie lewej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Przycięcie prawej krawędzi o <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Zapisano w aplikacji <xliff:g id="APP">%1$s</xliff:g> w profilu służbowym"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Zapisano w aplikacji <xliff:g id="APP">%1$s</xliff:g> w profilu prywatnym"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Pliki"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacja <xliff:g id="APPNAME">%1$s</xliff:g> wykryła ten zrzut ekranu."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Aplikacja <xliff:g id="APPNAME">%1$s</xliff:g> i inne aplikacje wykryły ten zrzut ekranu."</string> @@ -275,7 +276,7 @@ <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"rozłącz"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktywuj"</string> <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Automatycznie włącz ponownie jutro"</string> - <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Funkcje takie jak szybkie udostępnianie czy Znajdź moje urządzenie korzystają z Bluetootha"</string> + <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Bluetootha używają funkcje takie jak szybkie udostępnianie czy Znajdź moje urządzenie"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth włączy się jutro rano"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Udostępnianie dźwięku"</string> <string name="quick_settings_bluetooth_audio_sharing_button_sharing" msgid="8626191139359072540">"Udostępniam dźwięk"</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zamknij widżety na ekranie blokady"</string> <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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelita – połączenie słabe"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelita – połączenie dobre"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelita – połączenie dostępne"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satelitarne połączenie alarmowe"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil służbowy"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Dobra zabawa, ale nie dla każdego"</string> <string name="tuner_warning" msgid="1861736288458481650">"Kalibrator System UI udostępnia dodatkowe sposoby dostrajania i dostosowywania interfejsu Androida. Te eksperymentalne funkcje mogą się zmienić, popsuć lub zniknąć w przyszłych wersjach. Zachowaj ostrożność."</string> @@ -862,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodaj kartę"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Przenieś do pozycji <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodaj w pozycji <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nieprawidłowa pozycja."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozycja <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Dodano kartę"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Usunięto kartę"</string> diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml index 91b7a29b0add..425fa65b00fc 100644 --- a/packages/SystemUI/res/values-pt-rBR/strings.xml +++ b/packages/SystemUI/res/values-pt-rBR/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Borda esquerda em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Borda direita em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil particular"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"O app <xliff:g id="APPNAME">%1$s</xliff:g> detectou essa captura de tela."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outros apps abertos detectaram essa captura de tela."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Reativar apps de trabalho?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Reativar"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fechar widgets na tela de bloqueio"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -554,7 +559,7 @@ <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Seus apps pessoais estão conectados à Internet via <xliff:g id="VPN_APP">%1$s</xliff:g>. As atividades de rede, incluindo e-mails e dados de navegação, estão visíveis para o provedor de VPN."</string> <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string> <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Abrir configurações de VPN"</string> - <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Este dispositivo é gerenciado pelo seu familiar responsável, que pode ver e gerenciar informações como os apps que você usa, sua localização e seu tempo de uso."</string> + <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Este dispositivo é gerenciado pelo seu familiar responsável, que pode ver e gerenciar informações como os apps que você usa, sua localização e seu tempo de tela."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Desbloqueado pelo TrustAgent"</string> <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"O dispositivo foi bloqueado devido a muitas tentativas de autenticação"</string> @@ -591,7 +596,7 @@ <string name="screen_pinning_exit" msgid="4553787518387346893">"App liberado"</string> <string name="stream_voice_call" msgid="7468348170702375660">"Ligar"</string> <string name="stream_system" msgid="7663148785370565134">"Sistema"</string> - <string name="stream_ring" msgid="7550670036738697526">"Tocar"</string> + <string name="stream_ring" msgid="7550670036738697526">"Toques"</string> <string name="stream_music" msgid="2188224742361847580">"Mídia"</string> <string name="stream_alarm" msgid="16058075093011694">"Alarme"</string> <string name="stream_notification" msgid="7930294049046243939">"Notificação"</string> @@ -624,10 +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">"O áudio vai tocar em"</string> + <string name="media_output_title_without_playing" msgid="3825663683169305013">"Onde o áudio vai tocar?"</string> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, conexão ruim"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, conexão boa"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexão disponível"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satélite"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string> <string name="tuner_warning" msgid="1861736288458481650">"O sintonizador System UI fornece maneiras adicionais de ajustar e personalizar a interface do usuário do Android. Esses recursos experimentais podem mudar, falhar ou desaparecer nas versões futuras. Prossiga com cuidado."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar bloco"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover para <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloco adicionado"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloco removido"</string> diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml index 41d092ae4dd7..44531331bd68 100644 --- a/packages/SystemUI/res/values-pt-rPT/strings.xml +++ b/packages/SystemUI/res/values-pt-rPT/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Limite esquerdo de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Limite direito de <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Guardada na app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Guardada na app <xliff:g id="APP">%1$s</xliff:g> no perfil privado"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Ficheiros"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"A app <xliff:g id="APPNAME">%1$s</xliff:g> detetou esta captura de ecrã."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"A app <xliff:g id="APPNAME">%1$s</xliff:g> e outras apps abertas detetaram esta captura de ecrã."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Retomar apps de trabalho?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Retomar"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fechar widgets no ecrã de bloqueio"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +629,17 @@ <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> - <string name="media_output_label_title" msgid="872824698593182505">"A ouvir <xliff:g id="LABEL">%s</xliff:g> em"</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> <string name="system_ui_tuner" msgid="1471348823289954729">"Sintonizador da interface do sistema"</string> <string name="status_bar" msgid="4357390266055077437">"Barra de estado"</string> <string name="demo_mode" msgid="263484519766901593">"Modo de demonstração da IU do sistema"</string> @@ -655,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, ligação fraca"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, boa ligação"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, ligação disponível"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Ligação de satélite estabelecida"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Não tem uma ligação de satélite estabelecida"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satélite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string> <string name="tuner_warning" msgid="1861736288458481650">"O Sintonizador da interface do sistema disponibiliza-lhe formas adicionais ajustar e personalizar a interface do utilizador do Android. Estas funcionalidades experimentais podem ser alteradas, deixar de funcionar ou desaparecer em versões futuras. Prossiga com cuidado."</string> @@ -861,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar cartão"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mova para <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicione à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cartão adicionado"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cartão removido"</string> diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml index 91b7a29b0add..425fa65b00fc 100644 --- a/packages/SystemUI/res/values-pt/strings.xml +++ b/packages/SystemUI/res/values-pt/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Borda esquerda em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Borda direita em <xliff:g id="PERCENT">%1$d</xliff:g> por cento"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil de trabalho"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salva no app <xliff:g id="APP">%1$s</xliff:g> no perfil particular"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"O app <xliff:g id="APPNAME">%1$s</xliff:g> detectou essa captura de tela."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> e outros apps abertos detectaram essa captura de tela."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Reativar apps de trabalho?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Reativar"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Fechar widgets na tela de bloqueio"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -554,7 +559,7 @@ <string name="monitoring_description_personal_profile_named_vpn" msgid="5083909710727365452">"Seus apps pessoais estão conectados à Internet via <xliff:g id="VPN_APP">%1$s</xliff:g>. As atividades de rede, incluindo e-mails e dados de navegação, estão visíveis para o provedor de VPN."</string> <string name="monitoring_description_vpn_settings_separator" msgid="8292589617720435430">" "</string> <string name="monitoring_description_vpn_settings" msgid="5264167033247632071">"Abrir configurações de VPN"</string> - <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Este dispositivo é gerenciado pelo seu familiar responsável, que pode ver e gerenciar informações como os apps que você usa, sua localização e seu tempo de uso."</string> + <string name="monitoring_description_parental_controls" msgid="8184693528917051626">"Este dispositivo é gerenciado pelo seu familiar responsável, que pode ver e gerenciar informações como os apps que você usa, sua localização e seu tempo de tela."</string> <string name="legacy_vpn_name" msgid="4174223520162559145">"VPN"</string> <string name="keyguard_indication_trust_unlocked" msgid="7395154975733744547">"Desbloqueado pelo TrustAgent"</string> <string name="kg_prompt_after_adaptive_auth_lock" msgid="2587481497846342760">"O dispositivo foi bloqueado devido a muitas tentativas de autenticação"</string> @@ -591,7 +596,7 @@ <string name="screen_pinning_exit" msgid="4553787518387346893">"App liberado"</string> <string name="stream_voice_call" msgid="7468348170702375660">"Ligar"</string> <string name="stream_system" msgid="7663148785370565134">"Sistema"</string> - <string name="stream_ring" msgid="7550670036738697526">"Tocar"</string> + <string name="stream_ring" msgid="7550670036738697526">"Toques"</string> <string name="stream_music" msgid="2188224742361847580">"Mídia"</string> <string name="stream_alarm" msgid="16058075093011694">"Alarme"</string> <string name="stream_notification" msgid="7930294049046243939">"Notificação"</string> @@ -624,10 +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">"O áudio vai tocar em"</string> + <string name="media_output_title_without_playing" msgid="3825663683169305013">"Onde o áudio vai tocar?"</string> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satélite, conexão ruim"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satélite, conexão boa"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satélite, conexão disponível"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS via satélite"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Perfil de trabalho"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Diversão para alguns, mas não para todos"</string> <string name="tuner_warning" msgid="1861736288458481650">"O sintonizador System UI fornece maneiras adicionais de ajustar e personalizar a interface do usuário do Android. Esses recursos experimentais podem mudar, falhar ou desaparecer nas versões futuras. Prossiga com cuidado."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adicionar bloco"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mover para <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adicionar à posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Posição inválida."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posição <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Bloco adicionado"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Bloco removido"</string> diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml index 7ccaa5a9a7a4..596349ee57f1 100644 --- a/packages/SystemUI/res/values-ro/strings.xml +++ b/packages/SystemUI/res/values-ro/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Marginea stângă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Marginea dreaptă la <xliff:g id="PERCENT">%1$d</xliff:g> %%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Salvată în <xliff:g id="APP">%1$s</xliff:g> în profilul de serviciu"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Salvată în <xliff:g id="APP">%1$s</xliff:g> în profilul privat"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fișiere"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> a detectat această captură de ecran."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> și alte aplicații deschise au detectat această captură de ecran."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Reactivezi aplicații de lucru?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Reactivează"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Închide widgeturile de pe ecranul de blocare"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, conexiune slabă"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, conexiune bună"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, conexiune disponibilă"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS prin satelit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profil de serviciu"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Distractiv pentru unii, dar nu pentru toată lumea"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner oferă modalități suplimentare de a ajusta și a personaliza interfața de utilizare Android. Aceste funcții experimentale pot să se schimbe, să se blocheze sau să dispară din versiunile viitoare. Continuă cu prudență."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Adaugă un card"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Mută pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Adaugă pe poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Poziție nevalidă."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Poziția <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Cardul a fost adăugat"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Cardul a fost eliminat"</string> diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml index fad12e4e7361..6fabeae41a4a 100644 --- a/packages/SystemUI/res/values-ru/strings.xml +++ b/packages/SystemUI/res/values-ru/strings.xml @@ -97,6 +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> + <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> @@ -265,7 +266,7 @@ <string name="quick_settings_dnd_label" msgid="7728690179108024338">"Не беспокоить"</string> <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string> <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Нет доступных сопряженных устройств"</string> - <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Нажмите, чтобы подключить или отключить устройство"</string> + <string name="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">"Использовать"</string> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Спутниковый SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Рабочий профиль"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Внимание!"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner позволяет настраивать интерфейс устройства Android по вашему вкусу. В будущем эта экспериментальная функция может измениться, перестать работать или исчезнуть."</string> @@ -862,6 +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> + <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-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml index 0473cd657a9e..238e6c8e2b80 100644 --- a/packages/SystemUI/res/values-si/strings.xml +++ b/packages/SystemUI/res/values-si/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"චන්ද්රිකා SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"කාර්යාල පැතිකඩ"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"සමහරක් දේවල් වලට විනෝදයි, නමුත් සියල්ලටම නොවේ"</string> <string name="tuner_warning" msgid="1861736288458481650">"පද්ධති UI සුසරකය ඔබට Android පරිශීලක අතුරු මුහුණත වෙනස් කිරීමට හෝ අභිරුචිකරණය කිරීමට අමතර ක්රම ලබා දේ. මෙම පර්යේෂණාත්මක අංග ඉදිරි නිකුත් වීම් වල වෙනස් වීමට, වැඩ නොකිරීමට, හෝ නැතිවීමට හැක. ප්රවේශමෙන් ඉදිරියට යන්න."</string> @@ -862,6 +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> + <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-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml index 9d71dbd6dfab..825dba620870 100644 --- a/packages/SystemUI/res/values-sk/strings.xml +++ b/packages/SystemUI/res/values-sk/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> %% ľavej hranice"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> %% pravej hranice"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Uložená v aplikácii <xliff:g id="APP">%1$s</xliff:g> v pracovnom profile"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Uložené v aplikácii <xliff:g id="APP">%1$s</xliff:g> v súkromnom profile"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikácia <xliff:g id="APPNAME">%1$s</xliff:g> zaznamenala túto snímku obrazovky."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> a ďalšie otvorené aplikácie zaznamenali túto snímku obrazovky."</string> @@ -268,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> @@ -369,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> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zavrieť miniaplikácie na uzamknutej obrazovke"</string> <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> + <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> @@ -588,7 +594,7 @@ <string name="screen_pinning_negative" msgid="6882816864569211666">"Nie, vďaka"</string> <string name="screen_pinning_start" msgid="7483998671383371313">"Aplikácia bola pripnutá"</string> <string name="screen_pinning_exit" msgid="4553787518387346893">"Aplikácia bola odopnutá"</string> - <string name="stream_voice_call" msgid="7468348170702375660">"Zavolať"</string> + <string name="stream_voice_call" msgid="7468348170702375660">"Hovor"</string> <string name="stream_system" msgid="7663148785370565134">"Systém"</string> <string name="stream_ring" msgid="7550670036738697526">"Zvonenie"</string> <string name="stream_music" msgid="2188224742361847580">"Médiá"</string> @@ -613,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> @@ -623,10 +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> + <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> @@ -654,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, slabá kvalita pripojenia"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobrá kvalita pripojenia"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, pripojenie je k dispozícii"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Pomoc cez satelit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Pracovný profil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Pri používaní tuneru postupujte opatrne"</string> <string name="tuner_warning" msgid="1861736288458481650">"Tuner používateľského rozhrania systému poskytujte ďalšie spôsoby ladenia a prispôsobenia používateľského rozhrania Android. Tieto experimentálne funkcie sa môžu v budúcich verziách zmeniť, ich poskytovanie môže byť prerušené alebo môžu byť odstránené. Pokračujte opatrne."</string> @@ -862,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Pridať kartu"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Presunúť na <xliff:g id="POSITION">%1$d</xliff:g>. pozíciu"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Pridať na <xliff:g id="POSITION">%1$d</xliff:g>. pozíciu"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozícia je neplatná."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"<xliff:g id="POSITION">%1$d</xliff:g>. pozícia"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Karta bola pridaná"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Karta bola odstránená"</string> diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml index d41de93ce8c9..82f2df73a355 100644 --- a/packages/SystemUI/res/values-sl/strings.xml +++ b/packages/SystemUI/res/values-sl/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Meja levo <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Meja desno <xliff:g id="PERCENT">%1$d</xliff:g> odstotkov"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Shranjeno v aplikaciji <xliff:g id="APP">%1$s</xliff:g> v delovnem profilu"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Shranjeno v aplikaciji <xliff:g id="APP">%1$s</xliff:g> v zasebnem profilu"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Datoteke"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Aplikacija <xliff:g id="APPNAME">%1$s</xliff:g> je zaznala ta posnetek zaslona."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> in druge odprte aplikacije so zaznale ta posnetek zaslona."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Zapiranje pripomočkov na zaklenjenem zaslonu"</string> <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> + <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> @@ -623,10 +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> + <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> @@ -654,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satelit, slaba povezava"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satelit, dobra povezava"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satelit, povezava je na voljo"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Povezana s satelitom je vzpostavljena"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Povezana s satelitom ni vzpostavljena"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS prek satelita"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Delovni profil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Zabavno za nekatere, a ne za vse"</string> <string name="tuner_warning" msgid="1861736288458481650">"Uglaševalnik uporabniškega vmesnika sistema vam omogoča dodatne načine za spreminjanje in prilagajanje uporabniškega vmesnika Android. Te poskusne funkcije lahko v prihodnjih izdajah kadar koli izginejo, se spremenijo ali pokvarijo. Bodite previdni."</string> @@ -860,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Dodajanje ploščice"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Premik na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Dodajanje na položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Položaj je neveljaven."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Položaj <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ploščica je bila dodana"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ploščica je bila odstranjena"</string> diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml index 068a1a2e02ed..af2e3c788b5b 100644 --- a/packages/SystemUI/res/values-sq/strings.xml +++ b/packages/SystemUI/res/values-sq/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Kufiri i majtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Kufiri i djathtë <xliff:g id="PERCENT">%1$d</xliff:g> për qind"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Ruajtur në <xliff:g id="APP">%1$s</xliff:g> në profilin e punës"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Ruajtur në <xliff:g id="APP">%1$s</xliff:g> në profilin privat"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Skedarë"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> zbuloi këtë pamje ekrani."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> dhe aplikacionet e tjera të hapura zbuluan këtë pamje ekrani."</string> @@ -274,7 +275,7 @@ <string name="quick_settings_bluetooth_device_saved" msgid="7549938728928069477">"Ruajtur"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_disconnect" msgid="415980329093277342">"shkëput"</string> <string name="accessibility_quick_settings_bluetooth_device_tap_to_activate" msgid="3724301751036877403">"aktivizo"</string> - <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivizoje automatikisht nesër"</string> + <string name="turn_on_bluetooth_auto_tomorrow" msgid="414836329962473906">"Aktivizoje automatikisht sërish nesër"</string> <string name="turn_on_bluetooth_auto_info_disabled" msgid="682984290339848844">"Veçoritë e tilla si \"Ndarja e shpejtë\" dhe \"Gjej pajisjen time\" përdorin Bluetooth-in"</string> <string name="turn_on_bluetooth_auto_info_enabled" msgid="7440944034584560279">"Bluetooth-i do të aktivizohet nesër në mëngjes"</string> <string name="quick_settings_bluetooth_audio_sharing_button" msgid="4499275822759907822">"Ndarja e audios"</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Hiq nga pauza apl. e punës?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Hiq nga pauza"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Mbyll miniaplikacionet në ekranin e kyçjes"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> - <string name="media_output_label_title" msgid="872824698593182505">"Po luhet <xliff:g id="LABEL">%s</xliff:g> në"</string> - <string name="media_output_title_without_playing" msgid="3825663683169305013">"Do të luhet audio 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">"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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Sateliti. Lidhje e dobët"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Sateliti. Lidhje e mirë"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Sateliti. Ofrohet lidhje"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS satelitor"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profili i punës"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Argëtim për disa, por jo për të gjithë!"</string> <string name="tuner_warning" msgid="1861736288458481650">"Sintonizuesi i Sistemit të Ndërfaqes së Përdoruesit të jep mënyra shtesë për të tërhequr dhe personalizuar ndërfaqen Android të përdoruesit. Këto funksione eksperimentale mund të ndryshojnë, prishen ose zhduken në versionet e ardhshme. Vazhdo me kujdes."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Shto pllakëzën"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Zhvendos te <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Shto te pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozicion i pavlefshëm."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Pozicioni <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Pllakëza u shtua"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Pllakëza u hoq"</string> diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml index 0d33e3262034..42b979c0add9 100644 --- a/packages/SystemUI/res/values-sr/strings.xml +++ b/packages/SystemUI/res/values-sr/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Укључити пословне апликације?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Поново активирај"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Затворите виџете на закључаном екрану"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Повезано са сателитом"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Није повезано са сателитом"</string> + <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> @@ -861,6 +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> + <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-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml index 36cf28426e9f..29dca4630df8 100644 --- a/packages/SystemUI/res/values-sv/strings.xml +++ b/packages/SystemUI/res/values-sv/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Vänster gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Höger gräns: <xliff:g id="PERCENT">%1$d</xliff:g> procent"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Sparad i <xliff:g id="APP">%1$s</xliff:g> i jobbprofilen"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Sparad i <xliff:g id="APP">%1$s</xliff:g> i den privata profilen"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Filer"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> identifierade skärmbilden."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> och andra öppna appar identifierade skärmbilden."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Vill du återuppta jobbappar?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Återuppta"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Stäng widgetar på låsskärmen"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellit, dålig anslutning"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellit, bra anslutning"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellit, anslutning tillgänglig"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS-larm via satellit"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Jobbprofil"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Kul för vissa, inte för alla"</string> <string name="tuner_warning" msgid="1861736288458481650">"Du kan använda inställningarna för systemgränssnitt för att justera användargränssnittet i Android. Dessa experimentfunktioner kan när som helst ändras, sluta fungera eller försvinna. Använd med försiktighet."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Lägg till ruta"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Flytta till <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Lägg till på position <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Positionen är ogiltig."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Position <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kortet har lagts till"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kortet har tagits bort"</string> diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml index 34207f793e6b..9d5181e21b45 100644 --- a/packages/SystemUI/res/values-sw/strings.xml +++ b/packages/SystemUI/res/values-sw/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Mpaka wa sehemu ya kushoto wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Mpaka wa sehemu ya kulia wa asilimia <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Imehifadhiwa kwenye <xliff:g id="APP">%1$s</xliff:g> katika wasifu wa kazini"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Imehifadhiwa kwenye <xliff:g id="APP">%1$s</xliff:g> katika wasifu binafsi"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Faili"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> imetambua picha hii ya skrini."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> na zingine zinazotumika zimetambua picha hii ya skrini."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Je, ungependa kuacha kusitisha programu za kazini?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Acha kusitisha"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Funga wijeti kwenye skrini iliyofungwa"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Setilaiti, muunganisho hafifu"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Setilaiti, muunganisho thabiti"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Setilaiti, muunganisho unapatikana"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Umeunganisha kwenye setilaiti"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Hujaunganisha kwenye setilaiti"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Msaada kupitia Setilaiti"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Wasifu wa kazini"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Kinafurahisha kwa baadhi ya watu lakini si wote"</string> <string name="tuner_warning" msgid="1861736288458481650">"Kirekebishi cha kiolesura cha mfumo kinakupa njia zaidi za kugeuza na kubadilisha kiolesura cha Android ili kikufae. Vipengele hivi vya majaribio vinaweza kubadilika, kuharibika au kupotea katika matoleo ya siku zijazo. Endelea kwa uangalifu."</string> @@ -861,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Ongeza kigae"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Hamishia kwenye <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Ongeza kwenye nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Nafasi si sahihi."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Nafasi ya <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kigae kimewekwa"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kigae kimeondolewa"</string> diff --git a/packages/SystemUI/res/values-sw600dp-land/styles.xml b/packages/SystemUI/res/values-sw600dp-land/styles.xml index 0d46cbcf050e..cde1a1373bed 100644 --- a/packages/SystemUI/res/values-sw600dp-land/styles.xml +++ b/packages/SystemUI/res/values-sw600dp-land/styles.xml @@ -18,7 +18,7 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <style name="TextAppearance.AuthNonBioCredential.Title"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">16dp</item> <item name="android:textSize">36sp</item> <item name="android:focusable">true</item> @@ -26,14 +26,14 @@ </style> <style name="TextAppearance.AuthNonBioCredential.Subtitle"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">16dp</item> <item name="android:textSize">18sp</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> </style> <style name="TextAppearance.AuthNonBioCredential.Description"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">16dp</item> <item name="android:textSize">18sp</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> diff --git a/packages/SystemUI/res/values-sw600dp-port/styles.xml b/packages/SystemUI/res/values-sw600dp-port/styles.xml index 3add566e439c..85e7af6ee1de 100644 --- a/packages/SystemUI/res/values-sw600dp-port/styles.xml +++ b/packages/SystemUI/res/values-sw600dp-port/styles.xml @@ -26,7 +26,7 @@ </style> <style name="TextAppearance.AuthNonBioCredential.Title"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">24dp</item> <item name="android:textSize">36sp</item> <item name="android:focusable">true</item> diff --git a/packages/SystemUI/res/values-sw720dp-land/styles.xml b/packages/SystemUI/res/values-sw720dp-land/styles.xml index 7cdd07bec443..e75173d152b0 100644 --- a/packages/SystemUI/res/values-sw720dp-land/styles.xml +++ b/packages/SystemUI/res/values-sw720dp-land/styles.xml @@ -18,7 +18,7 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <style name="TextAppearance.AuthNonBioCredential.Title"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">16dp</item> <item name="android:textSize">36sp</item> <item name="android:focusable">true</item> @@ -26,14 +26,14 @@ </style> <style name="TextAppearance.AuthNonBioCredential.Subtitle"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">16dp</item> <item name="android:textSize">18sp</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> </style> <style name="TextAppearance.AuthNonBioCredential.Description"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">16dp</item> <item name="android:textSize">18sp</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> diff --git a/packages/SystemUI/res/values-sw720dp-port/styles.xml b/packages/SystemUI/res/values-sw720dp-port/styles.xml index 3add566e439c..85e7af6ee1de 100644 --- a/packages/SystemUI/res/values-sw720dp-port/styles.xml +++ b/packages/SystemUI/res/values-sw720dp-port/styles.xml @@ -26,7 +26,7 @@ </style> <style name="TextAppearance.AuthNonBioCredential.Title"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">24dp</item> <item name="android:textSize">36sp</item> <item name="android:focusable">true</item> diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml index 35861727f2f3..a2f4ce5424a2 100644 --- a/packages/SystemUI/res/values-ta/strings.xml +++ b/packages/SystemUI/res/values-ta/strings.xml @@ -97,6 +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> + <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">"Files"</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> @@ -274,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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"பணி ஆப்ஸை மீண்டும் இயக்கவா?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"மீண்டும் இயக்கு"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"பூட்டுத் திரையில் விட்ஜெட்களை மூடும்"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"சாட்டிலைட்டுடன் இணைக்கப்பட்டுள்ளது"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"சாட்டிலைட்டுடன் இணைக்கப்படவில்லை"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"சாட்டிலைட் SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"பணிக் கணக்கு"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"சில வேடிக்கையாக இருந்தாலும் கவனம் தேவை"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner, Android பயனர் இடைமுகத்தை மாற்றவும் தனிப்பயனாக்கவும் கூடுதல் வழிகளை வழங்குகிறது. இந்தப் பரிசோதனைக்குரிய அம்சங்கள் எதிர்கால வெளியீடுகளில் மாற்றப்படலாம், இடைநிறுத்தப்படலாம் அல்லது தோன்றாமல் போகலாம். கவனத்துடன் தொடரவும்."</string> @@ -861,6 +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> + <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-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml index 2d76896151ea..5b703a0d96fc 100644 --- a/packages/SystemUI/res/values-te/strings.xml +++ b/packages/SystemUI/res/values-te/strings.xml @@ -97,6 +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> + <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> @@ -271,7 +272,7 @@ <string name="turn_on_bluetooth" msgid="5681370462180289071">"బ్లూటూత్ వాడండి"</string> <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"కనెక్ట్ అయింది"</string> <string name="quick_settings_bluetooth_device_audio_sharing" msgid="1496358082943301670">"ఆడియో షేరింగ్"</string> - <string name="quick_settings_bluetooth_device_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">"రేపు మళ్లీ ఆటోమేటిక్గా ఆన్ చేస్తుంది"</string> @@ -368,7 +369,7 @@ <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_dialog_title" msgid="9004774017688484981">"వినికిడి పరికరం"</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> <string name="hearing_devices_presets_error" msgid="350363093458408536">"ప్రీసెట్ను అప్డేట్ చేయడం సాధ్యపడలేదు"</string> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> - <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 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> <string name="system_ui_tuner" msgid="1471348823289954729">"సిస్టమ్ UI ట్యూనర్"</string> <string name="status_bar" msgid="4357390266055077437">"స్టేటస్ బార్"</string> <string name="demo_mode" msgid="263484519766901593">"సిస్టమ్ UI డెమో మోడ్"</string> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"శాటిలైట్కు కనెక్ట్ చేయబడింది"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"శాటిలైట్కు కనెక్ట్ కాలేదు"</string> + <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">"సిస్టమ్ UI ట్యూనర్ Android వినియోగదారు ఇంటర్ఫేస్ను మెరుగుపరచడానికి మరియు అనుకూలంగా మార్చడానికి మీకు మరిన్ని మార్గాలను అందిస్తుంది. ఈ ప్రయోగాత్మక లక్షణాలు భవిష్యత్తు విడుదలల్లో మార్పుకు లోనవ్వచ్చు, తాత్కాలికంగా లేదా పూర్తిగా నిలిపివేయవచ్చు. జాగ్రత్తగా కొనసాగండి."</string> @@ -860,6 +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> + <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-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml index 7157e267b882..73ca20004444 100644 --- a/packages/SystemUI/res/values-th/strings.xml +++ b/packages/SystemUI/res/values-th/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"ยกเลิกการหยุดแอปงานชั่วคราวไหม"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"ยกเลิกการหยุดชั่วคราว"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"ปิดวิดเจ็ตในหน้าจอล็อก"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"เชื่อมต่อกับดาวเทียมแล้ว"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"ยังไม่ได้เชื่อมต่อกับดาวเทียม"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"SOS ดาวเทียม"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"โปรไฟล์งาน"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"เพลิดเพลินกับบางส่วนแต่ไม่ใช่ทั้งหมด"</string> <string name="tuner_warning" msgid="1861736288458481650">"ตัวรับสัญญาณ UI ระบบช่วยให้คุณมีวิธีพิเศษในการปรับแต่งและกำหนดค่าส่วนติดต่อผู้ใช้ Android ฟีเจอร์รุ่นทดลองเหล่านี้อาจมีการเปลี่ยนแปลง ขัดข้อง หรือหายไปในเวอร์ชันอนาคต โปรดดำเนินการด้วยความระมัดระวัง"</string> @@ -861,6 +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> + <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-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml index 51782a3d8fd9..0c616fa91dc1 100644 --- a/packages/SystemUI/res/values-tl/strings.xml +++ b/packages/SystemUI/res/values-tl/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa kaliwa"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"<xliff:g id="PERCENT">%1$d</xliff:g> (na) porsyento sa hangganan sa kanan"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Na-save sa <xliff:g id="APP">%1$s</xliff:g> sa profile sa trabaho"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Naka-save sa <xliff:g id="APP">%1$s</xliff:g> sa pribadong profile"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Mga File"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"Na-detect ng <xliff:g id="APPNAME">%1$s</xliff:g> ang screenshot. na ito"</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"Na-detect ng <xliff:g id="APPNAME">%1$s</xliff:g> at ng iba pang bukas na app ang screenshot na ito."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"I-unpause ang mga work app?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"I-unpause"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Isara ang mga widget sa lock screen"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +629,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"Tuner ng System UI"</string> <string name="status_bar" msgid="4357390266055077437">"Status bar"</string> <string name="demo_mode" msgid="263484519766901593">"Demo mode ng System UI"</string> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Satellite, mahina ang koneksyon"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Satellite, malakas ang koneksyon"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Satellite, may koneksyon"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Satellite SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Profile sa trabaho"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Masaya para sa ilan ngunit hindi para sa lahat"</string> <string name="tuner_warning" msgid="1861736288458481650">"Nagbibigay sa iyo ang Tuner ng System UI ng mga karagdagang paraan upang baguhin at i-customize ang user interface ng Android. Ang mga pang-eksperimentong feature na ito ay maaaring magbago, masira o mawala sa mga pagpapalabas sa hinaharap. Magpatuloy nang may pag-iingat."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Magdagdag ng tile"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Ilipat sa <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Idagdag sa posisyong <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Invalid ang posisyon."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Posisyon <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Idinagdag ang tile"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Inalis ang tile"</string> diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml index bf8581863d8f..6723c4226f04 100644 --- a/packages/SystemUI/res/values-tr/strings.xml +++ b/packages/SystemUI/res/values-tr/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Sol sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Sağ sınır yüzde <xliff:g id="PERCENT">%1$d</xliff:g>"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"İş profilinde <xliff:g id="APP">%1$s</xliff:g> uygulamasına kaydedildi"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Gizli profilde <xliff:g id="APP">%1$s</xliff:g> uygulamasına kaydedildi"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Dosyalar"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> bu ekran görüntüsünü algıladı."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ve diğer açık uygulamalar bu ekran görüntüsünü algıladı."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"İş uygulamaları devam ettirilsin mi?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Devam ettir"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Kilit ekranındaki widget\'ları kapat"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -591,14 +596,14 @@ <string name="screen_pinning_exit" msgid="4553787518387346893">"Uygulamanın sabitlemesi kaldırıldı"</string> <string name="stream_voice_call" msgid="7468348170702375660">"Çağrı"</string> <string name="stream_system" msgid="7663148785370565134">"Sistem"</string> - <string name="stream_ring" msgid="7550670036738697526">"Zili çaldır"</string> + <string name="stream_ring" msgid="7550670036738697526">"Zil"</string> <string name="stream_music" msgid="2188224742361847580">"Medya"</string> <string name="stream_alarm" msgid="16058075093011694">"Alarm"</string> <string name="stream_notification" msgid="7930294049046243939">"Bildirim"</string> <string name="stream_bluetooth_sco" msgid="6234562365528664331">"Bluetooth"</string> <string name="stream_dtmf" msgid="7322536356554673067">"Çift ton çoklu frekans"</string> <string name="stream_accessibility" msgid="3873610336741987152">"Erişilebilirlik"</string> - <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zili çaldır"</string> + <string name="volume_ringer_status_normal" msgid="1339039682222461143">"Zil"</string> <string name="volume_ringer_status_vibrate" msgid="6970078708957857825">"Titreşim"</string> <string name="volume_ringer_status_silent" msgid="3691324657849880883">"Sesi kapat"</string> <string name="media_device_cast" msgid="4786241789687569892">"Yayınla"</string> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Uydu, bağlantı zayıf"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Uydu, bağlantı güçlü"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Uydu, bağlantı mevcut"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Acil Uydu Bağlantısı"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"İş profili"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Bazıları için eğlenceliyken diğerleri için olmayabilir"</string> <string name="tuner_warning" msgid="1861736288458481650">"Sistem Kullanıcı Arayüzü Ayarlayıcı, Android kullanıcı arayüzünde değişiklikler yapmanız ve arayüzü özelleştirmeniz için ekstra yollar sağlar. Bu deneysel özellikler değişebilir, bozulabilir veya gelecekteki sürümlerde yer almayabilir. Dikkatli bir şekilde devam edin."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Kutu ekle"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"<xliff:g id="POSITION">%1$d</xliff:g> konumuna taşı"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"<xliff:g id="POSITION">%1$d</xliff:g> konumuna ekle"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Konum geçersiz."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Konum: <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Kutu eklendi"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Kutu kaldırıldı"</string> diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml index 71f1fb6277fe..2659f9fedfee 100644 --- a/packages/SystemUI/res/values-uk/strings.xml +++ b/packages/SystemUI/res/values-uk/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Увімкнути робочі додатки?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Увімкнути"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Закрити віджети на заблокованому екрані"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> - <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 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> <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> @@ -655,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Супутниковий сигнал SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Робочий профіль"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Це цікаво, але будьте обачні"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner пропонує нові способи налаштувати та персоналізувати інтерфейс користувача Android. Ці експериментальні функції можуть змінюватися, не працювати чи зникати в майбутніх версіях. Будьте обачні."</string> @@ -863,6 +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> + <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-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml index 0b79b1c5228b..2a7fda1791e5 100644 --- a/packages/SystemUI/res/values-ur/strings.xml +++ b/packages/SystemUI/res/values-ur/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,10 +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 (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"سیٹلائٹ SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"دفتری پروفائل"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"کچھ کیلئے دلچسپ لیکن سبھی کیلئے نہیں"</string> <string name="tuner_warning" msgid="1861736288458481650">"سسٹم UI ٹیونر Android صارف انٹر فیس میں ردوبدل کرنے اور اسے حسب ضرورت بنانے کیلئے آپ کو اضافی طریقے دیتا ہے۔ یہ تجرباتی خصوصیات مستقبل کی ریلیزز میں تبدیل ہو سکتی، رک سکتی یا غائب ہو سکتی ہیں۔ احتیاط کے ساتھ آگے بڑھیں۔"</string> @@ -862,6 +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> + <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-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml index 78def5df9991..0d7709c51775 100644 --- a/packages/SystemUI/res/values-uz/strings.xml +++ b/packages/SystemUI/res/values-uz/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Chap chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Oʻng chegara <xliff:g id="PERCENT">%1$d</xliff:g> foiz"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Ish profilidagi <xliff:g id="APP">%1$s</xliff:g> ilovasiga saqlandi"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Yopiq profildagi <xliff:g id="APP">%1$s</xliff:g> ilovasiga saqlandi"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Fayllar"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> skrinshot olinganini aniqladi."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> va boshqa ochiq ilovalar skrinshot olinganini aniqladi."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Ishga oid ilovalar qaytarilsinmi?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Davom ettirish"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Ekran qulfida vidjetlarni yopish"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +629,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"SystemUI Tuner"</string> <string name="status_bar" msgid="4357390266055077437">"Holat qatori"</string> <string name="demo_mode" msgid="263484519766901593">"Tizim interfeysi demo rejimi"</string> @@ -655,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Sputnik, aloqa sifati past"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Sputnik, aloqa sifati yaxshi"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Sputnik, aloqa mavjud"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Sputnikka ulangan"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Sputnikka ulanmagan"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Sputnik SOS"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Ish profili"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Diqqat!"</string> <string name="tuner_warning" msgid="1861736288458481650">"System UI Tuner yordamida siz Android foydalanuvchi interfeysini tuzatish va o‘zingizga moslashtirishingiz mumkin. Ushbu tajribaviy funksiyalar o‘zgarishi, buzilishi yoki keyingi versiyalarda olib tashlanishi mumkin. Ehtiyot bo‘lib davom eting."</string> @@ -861,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Katakcha kiritish"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Bu joyga olish: <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Bu joyga kiritish: <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Pozitsiya yaroqsiz."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Joylashuv: <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Katakcha kiritildi"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Katakcha olib tashlandi"</string> diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml index e50dd2fe1784..2ddcd6d32f44 100644 --- a/packages/SystemUI/res/values-vi/strings.xml +++ b/packages/SystemUI/res/values-vi/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Cạnh trái <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Cạnh phải <xliff:g id="PERCENT">%1$d</xliff:g>%%"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Đã lưu vào <xliff:g id="APP">%1$s</xliff:g> trong hồ sơ công việc"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Đã lưu vào <xliff:g id="APP">%1$s</xliff:g> trong hồ sơ riêng tư"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Files"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"<xliff:g id="APPNAME">%1$s</xliff:g> đã phát hiện thấy ảnh chụp màn hình này."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> và các ứng dụng đang mở khác đã phát hiện thấy ảnh chụp màn hình này."</string> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"Tiếp tục dùng ứng dụng công việc?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"Tiếp tục dùng"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Đóng các tiện ích trên màn hình khoá"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,10 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Kết nối vệ tinh kém"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Kết nối vệ tinh tốt"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Hiện có kết nối vệ tinh"</string> - <!-- no translation found for satellite_connected_carrier_text (7942466244369263272) --> - <skip /> - <!-- no translation found for satellite_not_connected_carrier_text (3471375076594005077) --> - <skip /> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Liên lạc khẩn cấp qua vệ tinh"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Hồ sơ công việc"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Thú vị đối với một số người nhưng không phải tất cả"</string> <string name="tuner_warning" msgid="1861736288458481650">"Bộ điều hướng giao diện người dùng hệ thống cung cấp thêm cho bạn những cách chỉnh sửa và tùy chỉnh giao diện người dùng Android. Những tính năng thử nghiệm này có thể thay đổi, hỏng hoặc biến mất trong các phiên bản tương lai. Hãy thận trọng khi tiếp tục."</string> @@ -863,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Thêm ô"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Di chuyển tới <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Thêm vào vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Vị trí không hợp lệ."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Vị trí <xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Đã thêm thẻ thông tin"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Đã xóa thẻ thông tin"</string> diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml index b0740a5b84ad..64e37ce4b88d 100644 --- a/packages/SystemUI/res/values-zh-rCN/strings.xml +++ b/packages/SystemUI/res/values-zh-rCN/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -457,9 +460,11 @@ <string name="work_mode_off_title" msgid="5794818421357835873">"是否为工作应用解除暂停状态?"</string> <string name="work_mode_turn_on" msgid="907813741770247267">"解除暂停"</string> <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"关闭锁定屏幕上的微件"</string> - <!-- no translation found for accessibility_action_label_edit_widgets (3821868581348322346) --> - <skip /> + <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> + <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> @@ -624,10 +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> + <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> @@ -655,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"已连接到卫星"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"未连接到卫星"</string> + <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> @@ -861,6 +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> + <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-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml index 94dd480d6b1c..714e479cb515 100644 --- a/packages/SystemUI/res/values-zh-rHK/strings.xml +++ b/packages/SystemUI/res/values-zh-rHK/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"已連上衛星"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"未連上衛星"</string> + <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> @@ -860,6 +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> + <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-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml index d072c7768c80..495a055c3e70 100644 --- a/packages/SystemUI/res/values-zh-rTW/strings.xml +++ b/packages/SystemUI/res/values-zh-rTW/strings.xml @@ -97,6 +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> + <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> @@ -446,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> @@ -459,6 +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> + <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> @@ -623,10 +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> + <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> @@ -654,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> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"已連上衛星"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"未連上衛星"</string> + <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> @@ -860,6 +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> + <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-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml index 6c746d3d5339..f5ee5a0d60b1 100644 --- a/packages/SystemUI/res/values-zu/strings.xml +++ b/packages/SystemUI/res/values-zu/strings.xml @@ -97,6 +97,7 @@ <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ongakwesobunxele"</string> <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"Iphesenti elingu-<xliff:g id="PERCENT">%1$d</xliff:g> lomngcele ongakwesokudla"</string> <string name="screenshot_work_profile_notification" msgid="203041724052970693">"Kulondolozwe ku-<xliff:g id="APP">%1$s</xliff:g> kuphrofayela yomsebenzi"</string> + <string name="screenshot_private_profile_notification" msgid="1704440899154243171">"Kulondolozwe kokuthi <xliff:g id="APP">%1$s</xliff:g> ephrofayeleni egodliwe"</string> <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"Amafayela"</string> <string name="screenshot_detected_template" msgid="7940376642921719915">"I-<xliff:g id="APPNAME">%1$s</xliff:g> ithole lesi sithombe-skrini."</string> <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"I-<xliff:g id="APPNAME">%1$s</xliff:g> namanye ama-app avuliwe athole lesi sithombe-skrini."</string> @@ -446,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> @@ -459,6 +462,9 @@ <string name="accessibility_action_label_close_communal_hub" msgid="6790396569621032333">"Vala amawijethi ekukhiyeni isikrini"</string> <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> + <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> @@ -623,10 +629,17 @@ <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> <string name="system_ui_tuner" msgid="1471348823289954729">"Isishuni se-UI yesistimu"</string> <string name="status_bar" msgid="4357390266055077437">"Ibha yesimo"</string> <string name="demo_mode" msgid="263484519766901593">"Imodi yedemo ye-UI yesistimu"</string> @@ -654,8 +667,7 @@ <string name="accessibility_status_bar_satellite_poor_connection" msgid="5231478574952724160">"Isathelayithi, uxhumano olungalungile"</string> <string name="accessibility_status_bar_satellite_good_connection" msgid="308079391708578704">"Isethelayithi, uxhumano oluhle"</string> <string name="accessibility_status_bar_satellite_available" msgid="6514855015496916829">"Isethelayithi, uxhumano luyatholakala"</string> - <string name="satellite_connected_carrier_text" msgid="7942466244369263272">"Ixhunywe esethelayithini"</string> - <string name="satellite_not_connected_carrier_text" msgid="3471375076594005077">"Ayixhunyiwe esethelayithini"</string> + <string name="satellite_connected_carrier_text" msgid="118524195198532589">"Isethelayithi yokuxhumana ngezimo eziphuthumayo"</string> <string name="accessibility_managed_profile" msgid="4703836746209377356">"Iphrofayela yomsebenzi"</string> <string name="tuner_warning_title" msgid="7721976098452135267">"Kuyajabulisa kwabanye kodwa hhayi bonke"</string> <string name="tuner_warning" msgid="1861736288458481650">"Isishuni se-UI sesistimu sikunika izindlela ezingeziwe zokuhlobisa nokwenza ngezifiso isixhumanisi sokubona se-Android. Lezi zici zesilingo zingashintsha, zephuke, noma zinyamalale ekukhishweni kwangakusasa. Qhubeka ngokuqaphela."</string> @@ -860,6 +872,7 @@ <string name="accessibility_qs_edit_tile_start_add" msgid="7560798153975555772">"Engeza ithayela"</string> <string name="accessibility_qs_edit_tile_move_to_position" msgid="5198161544045930556">"Hambisa ku-<xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_add_to_position" msgid="9029163095148274690">"Engeza kusikhundla se-<xliff:g id="POSITION">%1$d</xliff:g>"</string> + <string name="accessibilit_qs_edit_tile_add_move_invalid_position" msgid="2858467994472624487">"Indawo ayivumelekile."</string> <string name="accessibility_qs_edit_position" msgid="4509277359815711830">"Isikhundla se-<xliff:g id="POSITION">%1$d</xliff:g>"</string> <string name="accessibility_qs_edit_tile_added" msgid="9067146040380836334">"Ithayela lingeziwe"</string> <string name="accessibility_qs_edit_tile_removed" msgid="1175925632436612036">"Ithayela likhishiwe"</string> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 6bfd0887c404..2ba72e386cd1 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -635,9 +635,13 @@ 58.0001 29.2229,56.9551 26.8945,55.195 </string> - <!-- The time (in ms) needed to trigger the lock icon view's long-press affordance --> + <!-- The time (in ms) needed to trigger the device entry icon view's long-press affordance --> <integer name="config_lockIconLongPress" translatable="false">200</integer> + <!-- The time (in ms) needed to trigger the device entry icon view's long-press affordance + when the device supports an under-display fingerprint sensor --> + <integer name="config_udfpsDeviceEntryIconLongPress" translatable="false">100</integer> + <!-- package name of a built-in camera app to use to restrict implicit intent resolution when the double-press power gesture is used. Ignored if empty. --> <string translatable="false" name="config_cameraGesturePackage"></string> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a7a6d5b2f305..02b74ce14088 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> @@ -430,6 +433,7 @@ <dimen name="overlay_button_corner_radius">16dp</dimen> <!-- Margin between successive chips --> <dimen name="overlay_action_chip_margin_start">8dp</dimen> + <dimen name="shelf_action_chip_margin_start">12dp</dimen> <dimen name="overlay_action_chip_padding_vertical">12dp</dimen> <dimen name="overlay_action_chip_icon_size">24sp</dimen> <!-- Padding on each side of the icon for icon-only chips --> @@ -445,6 +449,12 @@ <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. Keep in sync with + negative_overlay_action_container_minimum_edge_spacing. --> + <dimen name="overlay_action_container_minimum_edge_spacing">12dp</dimen> + <!-- Keep in sync with overlay_action_container_minimum_edge_spacing --> + <dimen name="negative_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> @@ -907,10 +917,6 @@ obvious when corner radii differ.--> <dimen name="communal_enforced_rounded_corner_max_radius">28dp</dimen> - <!-- Width and height used to filter widgets displayed in the communal widget picker --> - <dimen name="communal_widget_picker_desired_width">424dp</dimen> - <dimen name="communal_widget_picker_desired_height">282dp</dimen> - <!-- The width/height of the unlock icon view on keyguard. --> <dimen name="keyguard_lock_height">42dp</dimen> <dimen name="keyguard_lock_padding">20dp</dimen> @@ -1707,12 +1713,12 @@ <dimen name="wallet_button_horizontal_padding">24dp</dimen> <dimen name="wallet_button_vertical_padding">8dp</dimen> - <!-- Ongoing call chip --> - <dimen name="ongoing_call_chip_side_padding">12dp</dimen> - <dimen name="ongoing_call_chip_icon_size">16dp</dimen> + <!-- Ongoing activity chip --> + <dimen name="ongoing_activity_chip_side_padding">12dp</dimen> + <dimen name="ongoing_activity_chip_icon_size">16dp</dimen> <!-- The padding between the icon and the text. --> - <dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen> - <dimen name="ongoing_call_chip_corner_radius">28dp</dimen> + <dimen name="ongoing_activity_chip_icon_text_padding">4dp</dimen> + <dimen name="ongoing_activity_chip_corner_radius">28dp</dimen> <!-- Status bar user chip --> <dimen name="status_bar_user_chip_avatar_size">16dp</dimen> @@ -1957,15 +1963,6 @@ <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen> <dimen name="broadcast_dialog_margin">16dp</dimen> - <!-- Contrast dialog --> - <dimen name="contrast_dialog_button_total_size">90dp</dimen> - <dimen name="contrast_dialog_button_inner_size">82dp</dimen> - <dimen name="contrast_dialog_button_radius">20dp</dimen> - <dimen name="contrast_dialog_button_stroke_width">4dp</dimen> - <dimen name="contrast_dialog_button_text_size">14sp</dimen> - <dimen name="contrast_dialog_button_text_spacing">4dp</dimen> - <dimen name="contrast_dialog_button_horizontal_spacing">16dp</dimen> - <!-- Shadow for dream overlay clock complication --> <dimen name="dream_overlay_clock_key_text_shadow_dx">0dp</dimen> <dimen name="dream_overlay_clock_key_text_shadow_dy">0dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index dcdd4f0f82b1..6df48a0d25fd 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] --> @@ -763,7 +765,7 @@ <!-- QuickSettings: Wifi secondary label shown when the wifi is being enabled [CHAR LIMIT=NONE] --> <string name="quick_settings_wifi_secondary_label_transient">Turning on…</string> <!-- QuickSettings: Cast title [CHAR LIMIT=NONE] --> - <string name="quick_settings_cast_title">Screen Cast</string> + <string name="quick_settings_cast_title">Cast</string> <!-- QuickSettings: Cast detail panel, status text when casting [CHAR LIMIT=NONE] --> <string name="quick_settings_casting">Casting</string> <!-- QuickSettings: Cast detail panel, default device name [CHAR LIMIT=NONE] --> @@ -900,15 +902,6 @@ <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] --> <string name="quick_settings_onehanded_label">One-handed mode</string> - <!-- QuickSettings: Contrast tile [CHAR LIMIT=NONE] --> - <string name="quick_settings_contrast_label">Contrast</string> - <!-- QuickSettings: Contrast tile description: standard [CHAR LIMIT=NONE] --> - <string name="quick_settings_contrast_standard">Standard</string> - <!-- QuickSettings: Contrast tile description: medium [CHAR LIMIT=NONE] --> - <string name="quick_settings_contrast_medium">Medium</string> - <!-- QuickSettings: Contrast tile description: high [CHAR LIMIT=NONE] --> - <string name="quick_settings_contrast_high">High</string> - <!-- Hearing devices --> <!-- QuickSettings: Hearing devices [CHAR LIMIT=NONE] --> <string name="quick_settings_hearing_devices_label">Hearing devices</string> @@ -1158,6 +1151,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] --> @@ -1186,6 +1181,10 @@ <string name="accessibility_content_description_for_communal_hub">Widgets on lock screen</string> <!-- Label for accessibility action to select a widget in edit mode. [CHAR LIMIT=NONE] --> <string name="accessibility_action_label_select_widget">select widget</string> + <!-- Label for accessibility action to remove a widget in edit mode. [CHAR LIMIT=NONE] --> + <string name="accessibility_action_label_remove_widget">remove widget</string> + <!-- Label for accessibility action to place a widget in edit mode after selecting move widget. [CHAR LIMIT=NONE] --> + <string name="accessibility_action_label_place_widget">place selected widget</string> <!-- Related to user switcher --><skip/> @@ -1630,9 +1629,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/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 69de45ea1acf..88135880f61b 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -175,21 +175,21 @@ </style> <style name="TextAppearance.AuthCredential.OldTitle"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:paddingTop">12dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">24sp</item> </style> <style name="TextAppearance.AuthCredential.OldSubtitle"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:paddingTop">8dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">16sp</item> </style> <style name="TextAppearance.AuthCredential.OldDescription"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:paddingTop">8dp</item> <item name="android:paddingHorizontal">24dp</item> <item name="android:textSize">14sp</item> @@ -205,7 +205,7 @@ </style> <style name="TextAppearance.AuthCredential.Title" parent="TextAppearance.Material3.HeadlineSmall" > - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> </style> @@ -257,7 +257,7 @@ </style> <style name="TextAppearance.AuthNonBioCredential.Title"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">24dp</item> <item name="android:textSize">36dp</item> <item name="android:focusable">true</item> @@ -265,14 +265,14 @@ </style> <style name="TextAppearance.AuthNonBioCredential.Subtitle"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">20dp</item> <item name="android:textSize">18sp</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> </style> <style name="TextAppearance.AuthNonBioCredential.Description"> - <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:layout_marginTop">20dp</item> <item name="android:textSize">18sp</item> <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> @@ -375,6 +375,12 @@ <item name="android:textColor">?androidprv:attr/materialColorPrimary</item> </style> + <style name="AuthCredentialNegativeButtonStyle" parent="TextAppearance.Material3.LabelLarge"> + <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> + <item name="android:background">@color/transparent</item> + <item name="android:textColor">?androidprv:attr/materialColorPrimary</item> + </style> + <style name="DeviceManagementDialogTitle"> <item name="android:gravity">center</item> <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item> @@ -523,10 +529,6 @@ <item name="android:windowExitAnimation">@anim/instant_fade_out</item> </style> - <style name="Theme.SystemUI.ContrastDialog" parent="@android:style/Theme.DeviceDefault.Dialog"> - <item name="android:windowBackground">@android:color/transparent</item> - </style> - <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings"> </style> 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/keyguard/src/com/android/keyguard/BasePasswordTextView.java b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java index fe12134054c4..863f0c35490e 100644 --- a/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java +++ b/packages/SystemUI/shared/keyguard/src/com/android/keyguard/BasePasswordTextView.java @@ -231,7 +231,7 @@ public abstract class BasePasswordTextView extends FrameLayout { info.setClassName(EditText.class.getName()); info.setPassword(true); info.setText(getTransformedText()); - + info.setSelected(false); info.setEditable(true); info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java index 8ac1de898be8..c33b7ce1d002 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java @@ -99,7 +99,7 @@ public class PipSurfaceTransactionHelper { final float startScale = sourceRectHint.width() <= sourceRectHint.height() ? (float) destinationBounds.width() / sourceBounds.width() : (float) destinationBounds.height() / sourceBounds.height(); - scale = (1 - progress) * startScale + progress * endScale; + scale = Math.min((1 - progress) * startScale + progress * endScale, 1.0f); } final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale; final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale; 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/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java deleted file mode 100644 index 0b0df833e916..000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.shared.recents.model; - -import static android.app.WindowConfiguration.ROTATION_UNDEFINED; -import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.res.Configuration.ORIENTATION_UNDEFINED; -import static android.graphics.Bitmap.Config.ARGB_8888; - -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.HardwareBuffer; -import android.util.Log; -import android.view.WindowInsetsController.Appearance; -import android.window.TaskSnapshot; - -import java.util.HashMap; - -/** - * Data for a single thumbnail. - */ -public class ThumbnailData { - - public final Bitmap thumbnail; - public int orientation; - public int rotation; - public Rect insets; - public Rect letterboxInsets; - public boolean reducedResolution; - public boolean isRealSnapshot; - public boolean isTranslucent; - public int windowingMode; - public @Appearance int appearance; - public float scale; - public long snapshotId; - - public ThumbnailData() { - thumbnail = null; - orientation = ORIENTATION_UNDEFINED; - rotation = ROTATION_UNDEFINED; - insets = new Rect(); - letterboxInsets = new Rect(); - reducedResolution = false; - scale = 1f; - isRealSnapshot = true; - isTranslucent = false; - windowingMode = WINDOWING_MODE_UNDEFINED; - snapshotId = 0; - } - - public void recycleBitmap() { - if (thumbnail != null) { - thumbnail.recycle(); - } - } - - private static Bitmap makeThumbnail(TaskSnapshot snapshot) { - Bitmap thumbnail = null; - try (final HardwareBuffer buffer = snapshot.getHardwareBuffer()) { - if (buffer != null) { - thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.getColorSpace()); - } - } catch (IllegalArgumentException ex) { - // TODO(b/157562905): Workaround for a crash when we get a snapshot without this state - Log.e("ThumbnailData", "Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: " - + snapshot.getHardwareBuffer(), ex); - } - if (thumbnail == null) { - Point taskSize = snapshot.getTaskSize(); - thumbnail = Bitmap.createBitmap(taskSize.x, taskSize.y, ARGB_8888); - thumbnail.eraseColor(Color.BLACK); - } - return thumbnail; - } - - public static HashMap<Integer, ThumbnailData> wrap(int[] taskIds, TaskSnapshot[] snapshots) { - HashMap<Integer, ThumbnailData> temp = new HashMap<>(); - if (taskIds == null || snapshots == null || taskIds.length != snapshots.length) { - return temp; - } - - for (int i = snapshots.length - 1; i >= 0; i--) { - temp.put(taskIds[i], new ThumbnailData(snapshots[i])); - } - return temp; - } - - public ThumbnailData(TaskSnapshot snapshot) { - thumbnail = makeThumbnail(snapshot); - insets = new Rect(snapshot.getContentInsets()); - letterboxInsets = new Rect(snapshot.getLetterboxInsets()); - orientation = snapshot.getOrientation(); - rotation = snapshot.getRotation(); - reducedResolution = snapshot.isLowResolution(); - // TODO(b/149579527): Pass task size instead of computing scale. - // Assume width and height were scaled the same; compute scale only for width - scale = (float) thumbnail.getWidth() / snapshot.getTaskSize().x; - isRealSnapshot = snapshot.isRealSnapshot(); - isTranslucent = snapshot.isTranslucent(); - windowingMode = snapshot.getWindowingMode(); - appearance = snapshot.getAppearance(); - snapshotId = snapshot.getId(); - } -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt new file mode 100644 index 000000000000..dcf7754221bb --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.shared.recents.model + +import android.app.WindowConfiguration +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.Bitmap.Config.ARGB_8888 +import android.graphics.Color +import android.graphics.Rect +import android.util.Log +import android.view.WindowInsetsController.Appearance +import android.window.TaskSnapshot + +/** Data for a single thumbnail. */ +data class ThumbnailData( + val thumbnail: Bitmap? = null, + var orientation: Int = Configuration.ORIENTATION_UNDEFINED, + @JvmField var rotation: Int = WindowConfiguration.ROTATION_UNDEFINED, + @JvmField var insets: Rect = Rect(), + @JvmField var letterboxInsets: Rect = Rect(), + @JvmField var reducedResolution: Boolean = false, + @JvmField var isRealSnapshot: Boolean = true, + var isTranslucent: Boolean = false, + @JvmField var windowingMode: Int = WindowConfiguration.WINDOWING_MODE_UNDEFINED, + @JvmField @Appearance var appearance: Int = 0, + @JvmField var scale: Float = 1f, + var snapshotId: Long = 0, +) { + fun recycleBitmap() { + thumbnail?.recycle() + } + + companion object { + private fun makeThumbnail(snapshot: TaskSnapshot): Bitmap { + var thumbnail: Bitmap? = null + try { + snapshot.hardwareBuffer?.use { buffer -> + thumbnail = Bitmap.wrapHardwareBuffer(buffer, snapshot.colorSpace) + } + } catch (ex: IllegalArgumentException) { + // TODO(b/157562905): Workaround for a crash when we get a snapshot without this + // state + Log.e( + "ThumbnailData", + "Unexpected snapshot without USAGE_GPU_SAMPLED_IMAGE: " + + "${snapshot.hardwareBuffer}", + ex + ) + } + + return thumbnail + ?: Bitmap.createBitmap(snapshot.taskSize.x, snapshot.taskSize.y, ARGB_8888).apply { + eraseColor(Color.BLACK) + } + } + + @JvmStatic + fun wrap(taskIds: IntArray?, snapshots: Array<TaskSnapshot>?): HashMap<Int, ThumbnailData> { + return if (taskIds == null || snapshots == null || taskIds.size != snapshots.size) { + HashMap() + } else { + HashMap(taskIds.associateWith { taskId -> fromSnapshot(snapshots[taskId]) }) + } + } + + @JvmStatic + fun fromSnapshot(snapshot: TaskSnapshot): ThumbnailData { + val thumbnail = makeThumbnail(snapshot) + return ThumbnailData( + thumbnail = thumbnail, + insets = Rect(snapshot.contentInsets), + letterboxInsets = Rect(snapshot.letterboxInsets), + orientation = snapshot.orientation, + rotation = snapshot.rotation, + reducedResolution = snapshot.isLowResolution, + // TODO(b/149579527): Pass task size instead of computing scale. + // Assume width and height were scaled the same; compute scale only for width + scale = thumbnail.width.toFloat() / snapshot.taskSize.x, + isRealSnapshot = snapshot.isRealSnapshot, + isTranslucent = snapshot.isTranslucent, + windowingMode = snapshot.windowingMode, + appearance = snapshot.appearance, + snapshotId = snapshot.id, + ) + } + } +} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index ca63483f656a..845ca5e8b9ec 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -147,7 +147,7 @@ public class ActivityManagerWrapper { Log.w(TAG, "Failed to retrieve task snapshot", e); } if (snapshot != null) { - return new ThumbnailData(snapshot); + return ThumbnailData.fromSnapshot(snapshot); } else { return new ThumbnailData(); } @@ -167,7 +167,7 @@ public class ActivityManagerWrapper { Log.w(TAG, "Failed to take task snapshot", e); } if (snapshot != null) { - return new ThumbnailData(snapshot); + return ThumbnailData.fromSnapshot(snapshot); } else { return new ThumbnailData(); } 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 c08b0837da8e..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_TRACING_ENABLED = 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, @@ -148,7 +150,7 @@ public class QuickStepContract { SYSUI_STATE_OVERVIEW_DISABLED, SYSUI_STATE_HOME_DISABLED, SYSUI_STATE_SEARCH_DISABLED, - SYSUI_STATE_TRACING_ENABLED, + SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION, SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED, SYSUI_STATE_BUBBLES_EXPANDED, SYSUI_STATE_DIALOG_SHOWING, @@ -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"); @@ -211,8 +214,8 @@ public class QuickStepContract { if ((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0) { str.add("a11y_long_click"); } - if ((flags & SYSUI_STATE_TRACING_ENABLED) != 0) { - str.add("tracing"); + if ((flags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) != 0) { + str.add("disable_gesture_split_invocation"); } if ((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0) { str.add("asst_gesture_constrain"); @@ -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/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index a6e04cec5f86..bbf46984208f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -42,7 +42,7 @@ public class RecentsAnimationControllerCompat { try { final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); if (snapshot != null) { - return new ThumbnailData(snapshot); + return ThumbnailData.fromSnapshot(snapshot); } } catch (RemoteException e) { Log.e(TAG, "Failed to screenshot task", e); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java index c613afbda5b8..cf8ec62f19ef 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java @@ -141,6 +141,7 @@ public class TaskStackChangeListeners { private static final int ON_TASK_DESCRIPTION_CHANGED = 21; private static final int ON_ACTIVITY_ROTATION = 22; private static final int ON_LOCK_TASK_MODE_CHANGED = 23; + private static final int ON_TASK_SNAPSHOT_INVALIDATED = 24; /** * List of {@link TaskStackChangeListener} registered from {@link #addListener}. @@ -272,6 +273,12 @@ public class TaskStackChangeListeners { } @Override + public void onTaskSnapshotInvalidated(int taskId) { + mHandler.obtainMessage(ON_TASK_SNAPSHOT_INVALIDATED, taskId, 0 /* unused */) + .sendToTarget(); + } + + @Override public void onTaskCreated(int taskId, ComponentName componentName) { mHandler.obtainMessage(ON_TASK_CREATED, taskId, 0, componentName).sendToTarget(); } @@ -344,7 +351,7 @@ public class TaskStackChangeListeners { case ON_TASK_SNAPSHOT_CHANGED: { Trace.beginSection("onTaskSnapshotChanged"); final TaskSnapshot snapshot = (TaskSnapshot) msg.obj; - final ThumbnailData thumbnail = new ThumbnailData(snapshot); + final ThumbnailData thumbnail = ThumbnailData.fromSnapshot(snapshot); boolean snapshotConsumed = false; for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { boolean consumed = mTaskStackListeners.get(i).onTaskSnapshotChanged( @@ -496,6 +503,15 @@ public class TaskStackChangeListeners { } break; } + case ON_TASK_SNAPSHOT_INVALIDATED: { + Trace.beginSection("onTaskSnapshotInvalidated"); + final ThumbnailData thumbnail = new ThumbnailData(); + for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) { + mTaskStackListeners.get(i).onTaskSnapshotChanged(msg.arg1, thumbnail); + } + Trace.endSection(); + break; + } } } if (msg.obj instanceof SomeArgs) { diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 460779c73cda..3f3bb0bc94b6 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -42,7 +42,9 @@ import com.android.systemui.flags.Flags.REGION_SAMPLING 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.Edge 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 +434,7 @@ constructor( listenForDozeAmountTransition(this) listenForAnyStateToAodTransition(this) listenForAnyStateToLockscreenTransition(this) + listenForAnyStateToDozingTransition(this) } else { listenForDozeAmount(this) } @@ -542,10 +545,10 @@ constructor( internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { return scope.launch { merge( - keyguardTransitionInteractor.transition(AOD, LOCKSCREEN).map { step -> - step.copy(value = 1f - step.value) + keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)).map { + it.copy(value = 1f - it.value) }, - keyguardTransitionInteractor.transition(LOCKSCREEN, AOD), + keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)), ).filter { it.transitionState != TransitionState.FINISHED } @@ -578,6 +581,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/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 91fb6888bf06..42838aeddd6b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -35,6 +35,7 @@ import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; +import android.app.admin.flags.Flags; import android.content.Intent; import android.content.res.ColorStateList; import android.content.res.Configuration; @@ -134,6 +135,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final BouncerMessageInteractor mBouncerMessageInteractor; private int mTranslationY; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; + private final DevicePolicyManager mDevicePolicyManager; // Whether the volume keys should be handled by keyguard. If true, then // they will be handled here for specific media types such as music, otherwise // the audio service will bring up the volume dialog. @@ -324,7 +326,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardTransitionInteractor.startDismissKeyguardTransition(); + mKeyguardTransitionInteractor.startDismissKeyguardTransition( + "KeyguardSecurityContainerController#finish"); } } @@ -460,6 +463,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard SelectedUserInteractor selectedUserInteractor, DeviceProvisionedController deviceProvisionedController, FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate, + DevicePolicyManager devicePolicyManager, KeyguardTransitionInteractor keyguardTransitionInteractor, Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor, Provider<DeviceEntryInteractor> deviceEntryInteractor @@ -495,6 +499,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mKeyguardTransitionInteractor = keyguardTransitionInteractor; mDeviceProvisionedController = deviceProvisionedController; mPrimaryBouncerInteractor = primaryBouncerInteractor; + mDevicePolicyManager = devicePolicyManager; } @Override @@ -1105,35 +1110,23 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts); - final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager(); final int failedAttemptsBeforeWipe = - dpm.getMaximumFailedPasswordsForWipe(null, userId); + mDevicePolicyManager.getMaximumFailedPasswordsForWipe(null, userId); final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ? (failedAttemptsBeforeWipe - failedAttempts) : Integer.MAX_VALUE; // because DPM returns 0 if no restriction if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) { - // The user has installed a DevicePolicyManager that requests a user/profile to be wiped - // N attempts. Once we get below the grace period, we post this dialog every time as a - // clear warning until the deletion fires. - // Check which profile has the strictest policy for failed password attempts - final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId); - int userType = USER_TYPE_PRIMARY; - if (expiringUser == userId) { - // TODO: http://b/23522538 - if (expiringUser != UserHandle.USER_SYSTEM) { - userType = USER_TYPE_SECONDARY_USER; - } - } else if (expiringUser != UserHandle.USER_NULL) { - userType = USER_TYPE_WORK_PROFILE; - } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY - if (remainingBeforeWipe > 0) { - mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType); - } else { - // Too many attempts. The device will be wiped shortly. - Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!"); - mView.showWipeDialog(failedAttempts, userType); - } + // The user has installed a DevicePolicyManager that requests a + // user/profile to be wiped N attempts. Once we get below the grace period, + // we post this dialog every time as a clear warning until the deletion + // fires. Check which profile has the strictest policy for failed password + // attempts. + final int expiringUser = + mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(userId); + Integer mainUser = mSelectedUserInteractor.getMainUserId(); + showMessageForFailedUnlockAttempt( + userId, expiringUser, mainUser, remainingBeforeWipe, failedAttempts); } mLockPatternUtils.reportFailedPasswordAttempt(userId); if (timeoutMs > 0) { @@ -1145,6 +1138,35 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } + @VisibleForTesting + void showMessageForFailedUnlockAttempt(int userId, int expiringUserId, Integer mainUserId, + int remainingBeforeWipe, int failedAttempts) { + int userType = USER_TYPE_PRIMARY; + if (expiringUserId == userId) { + int primaryUser = UserHandle.USER_SYSTEM; + if (Flags.headlessSingleUserFixes()) { + if (mainUserId != null) { + primaryUser = mainUserId; + } + } + // TODO: http://b/23522538 + if (expiringUserId != primaryUser) { + userType = USER_TYPE_SECONDARY_USER; + } + } else if (expiringUserId != UserHandle.USER_NULL) { + userType = USER_TYPE_WORK_PROFILE; + } // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY + if (remainingBeforeWipe > 0) { + mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, + userType); + } else { + // Too many attempts. The device will be wiped shortly. + Slog.i(TAG, "Too many unlock attempts; user " + expiringUserId + + " will be wiped!"); + mView.showWipeDialog(failedAttempts, userType); + } + } + private void getCurrentSecurityController( KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback) { mSecurityViewFlipperController diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java index 57c1fd091ae9..42896a419658 100644 --- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java +++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java @@ -569,6 +569,11 @@ public class ExpandHelper implements Gefingerpoken { return true; } + /** Finish the current expand motion without accounting for velocity. */ + public void finishExpanding() { + finishExpanding(false, 0); + } + /** * Finish the current expand motion * @param forceAbort whether the expansion should be forcefully aborted and returned to the old diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt index 35f9344ae897..004d5dba64c7 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt @@ -22,6 +22,8 @@ import com.android.systemui.accessibility.data.repository.ColorCorrectionReposit import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl import com.android.systemui.accessibility.data.repository.ColorInversionRepository import com.android.systemui.accessibility.data.repository.ColorInversionRepositoryImpl +import com.android.systemui.accessibility.data.repository.OneHandedModeRepository +import com.android.systemui.accessibility.data.repository.OneHandedModeRepositoryImpl import com.android.systemui.accessibility.qs.QSAccessibilityModule import dagger.Binds import dagger.Module @@ -34,6 +36,8 @@ interface AccessibilityModule { @Binds fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository + @Binds fun oneHandedModeRepository(impl: OneHandedModeRepositoryImpl): OneHandedModeRepository + @Binds fun accessibilityQsShortcutsRepository( impl: AccessibilityQsShortcutsRepositoryImpl 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/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java index e66261c459c7..5458ab1196d7 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java @@ -240,7 +240,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold private boolean mEditSizeEnable = false; private boolean mSettingsPanelVisibility = false; @VisibleForTesting - WindowMagnificationSizePrefs mWindowMagnificationSizePrefs; + WindowMagnificationFrameSizePrefs mWindowMagnificationFrameSizePrefs; @Nullable private final MirrorWindowControl mMirrorWindowControl; @@ -270,7 +270,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold mSysUiState = sysUiState; mScvhSupplier = scvhSupplier; mConfiguration = new Configuration(context.getResources().getConfiguration()); - mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext); + mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext); final Display display = mContext.getDisplay(); mDisplayId = mContext.getDisplayId(); @@ -457,7 +457,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold if (!enable) { // Keep the magnifier size when exiting edit mode - mWindowMagnificationSizePrefs.saveSizeForCurrentDensity( + mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity( new Size(mMagnificationFrame.width(), mMagnificationFrame.height())); } } @@ -944,7 +944,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } private void setMagnificationFrame(int width, int height, int centerX, int centerY) { - mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(new Size(width, height)); + mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(new Size(width, height)); // Sets the initial frame area for the mirror and place it to the given center on the // display. @@ -954,11 +954,11 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold } private Size restoreMagnificationWindowFrameSizeIfPossible() { - if (!mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity()) { + if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) { return getDefaultMagnificationWindowFrameSize(); } - return mWindowMagnificationSizePrefs.getSizeForCurrentDensity(); + return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity(); } private Size getDefaultMagnificationWindowFrameSize() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java index a401f2a980c1..e83e85e1af1a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSizePrefs.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefs.java @@ -23,14 +23,14 @@ import android.util.Size; /** * Class to handle SharedPreference for window magnification size. */ -final class WindowMagnificationSizePrefs { +final class WindowMagnificationFrameSizePrefs { private static final String WINDOW_MAGNIFICATION_PREFERENCES = "window_magnification_preferences"; Context mContext; SharedPreferences mWindowMagnificationSizePreferences; - public WindowMagnificationSizePrefs(Context context) { + WindowMagnificationFrameSizePrefs(Context context) { mContext = context; mWindowMagnificationSizePreferences = mContext .getSharedPreferences(WINDOW_MAGNIFICATION_PREFERENCES, Context.MODE_PRIVATE); 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..b33746c7a25f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/NightDisplayRepository.kt @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 { isNightDisplayAutoModeRawSettingNotSet(userHandle.identifier) } + } + .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 && + isNightDisplayAutoModeRawSettingNotSet(user.identifier), + locationController.isLocationEnabled, + ) + } + + private fun isNightDisplayAutoModeRawSettingNotSet(userId: Int): Boolean { + return secureSettings.getIntForUser( + DISPLAY_AUTO_MODE_RAW_SETTING_NAME, + NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET, + userId + ) == NIGHT_DISPLAY_AUTO_MODE_RAW_NOT_SET + } + + 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/data/repository/OneHandedModeRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt new file mode 100644 index 000000000000..d921025ba151 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepository.kt @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.os.UserHandle +import android.provider.Settings +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.settings.SettingsProxyExt.observerFlow +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext + +/** Provides data related to one handed mode. */ +interface OneHandedModeRepository { + /** Observable for whether one handed mode is enabled */ + fun isEnabled(userHandle: UserHandle): Flow<Boolean> + + /** Sets one handed mode enabled state. */ + suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean +} + +@SysUISingleton +class OneHandedModeRepositoryImpl +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, + private val secureSettings: SecureSettings, +) : OneHandedModeRepository { + + private val userMap = mutableMapOf<Int, Flow<Boolean>>() + + override fun isEnabled(userHandle: UserHandle): Flow<Boolean> = + userMap.getOrPut(userHandle.identifier) { + secureSettings + .observerFlow(userHandle.identifier, SETTING_NAME) + .onStart { emit(Unit) } + .map { + secureSettings.getIntForUser(SETTING_NAME, DISABLED, userHandle.identifier) == + ENABLED + } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_VALUE) + } + + override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean = + withContext(bgCoroutineContext) { + secureSettings.putIntForUser( + SETTING_NAME, + if (isEnabled) ENABLED else DISABLED, + userHandle.identifier + ) + } + + companion object { + private const val SETTING_NAME = Settings.Secure.ONE_HANDED_MODE_ENABLED + private const val DISABLED = 0 + private const val ENABLED = 1 + private const val DEFAULT_VALUE = false + } +} 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/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index 1f0459978c3c..d5e911efe570 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -37,7 +37,6 @@ import androidx.dynamicanimation.animation.SpringForce; import androidx.recyclerview.widget.RecyclerView; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.Flags; import java.util.HashMap; @@ -339,15 +338,11 @@ class MenuAnimationController { mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true); final PointF position = mMenuView.getMenuPosition(); final PointF tuckedPosition = getTuckedMenuPosition(); - if (Flags.floatingMenuAnimatedTuck()) { - flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X, - Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY, - FLING_FRICTION_SCALAR, - createDefaultSpringForce(), - tuckedPosition.x); - } else { - moveToPosition(tuckedPosition); - } + flingThenSpringMenuWith(DynamicAnimation.TRANSLATION_X, + Math.signum(tuckedPosition.x - position.x) * ESCAPE_VELOCITY, + FLING_FRICTION_SCALAR, + createDefaultSpringForce(), + tuckedPosition.x); // Keep the touch region let users could click extra space to pop up the menu view // from the screen edge @@ -359,23 +354,19 @@ class MenuAnimationController { void moveOutEdgeAndShow() { mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false); - if (Flags.floatingMenuAnimatedTuck()) { - PointF position = mMenuView.getMenuPosition(); - springMenuWith(DynamicAnimation.TRANSLATION_X, - createDefaultSpringForce(), - 0, - position.x, - true - ); - springMenuWith(DynamicAnimation.TRANSLATION_Y, - createDefaultSpringForce(), - 0, - position.y, - true - ); - } else { - mMenuView.onPositionChanged(); - } + PointF position = mMenuView.getMenuPosition(); + springMenuWith(DynamicAnimation.TRANSLATION_X, + createDefaultSpringForce(), + 0, + position.x, + true + ); + springMenuWith(DynamicAnimation.TRANSLATION_Y, + createDefaultSpringForce(), + 0, + position.y, + true + ); mMenuView.onEdgeChangedIfNeeded(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index be75e1035ea6..9d9e7dfb7032 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -321,22 +321,6 @@ class MenuView extends FrameLayout implements if (mMoveToTuckedListener != null) { mMoveToTuckedListener.onMoveToTuckedChanged(isMoveToTucked); } - - if (!Flags.floatingMenuAnimatedTuck()) { - if (isMoveToTucked) { - final float halfWidth = getMenuWidth() / 2.0f; - final boolean isOnLeftSide = mMenuAnimationController.isOnLeftSide(); - final Rect clipBounds = new Rect( - (int) (!isOnLeftSide ? 0 : halfWidth), - 0, - (int) (!isOnLeftSide ? halfWidth : getMenuWidth()), - getMenuHeight() - ); - setClipBounds(clipBounds); - } else { - setClipBounds(null); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java index 6dce1bb22921..0c67c5093faf 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -322,9 +322,8 @@ class MenuViewLayer extends FrameLayout implements } addView(mMessageView, LayerIndex.MESSAGE_VIEW); - if (Flags.floatingMenuAnimatedTuck()) { - setClipChildren(true); - } + setClipChildren(true); + setClickable(false); setFocusable(false); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); @@ -476,10 +475,8 @@ class MenuViewLayer extends FrameLayout implements mMenuAnimationController.startTuckedAnimationPreview(); } - if (Flags.floatingMenuAnimatedTuck()) { - if (!mMenuView.isMoveToTucked()) { - setClipBounds(null); - } + if (!mMenuView.isMoveToTucked()) { + setClipBounds(null); } mMenuView.onArrivalAtPosition(false); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java index 623b40f144eb..14e5f3422a27 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManager.java @@ -18,7 +18,6 @@ package com.android.systemui.accessibility.hearingaid; import android.bluetooth.BluetoothDevice; import android.util.Log; -import android.view.View; import androidx.annotation.Nullable; @@ -26,6 +25,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -58,9 +58,9 @@ public class HearingDevicesDialogManager { /** * Shows the dialog. * - * @param view The view from which the dialog is shown. + * @param expandable {@link Expandable} from which the dialog is shown. */ - public void showDialog(View view) { + public void showDialog(Expandable expandable) { if (mDialog != null) { if (DEBUG) { Log.d(TAG, "HearingDevicesDialog already showing. Destroy it first."); @@ -70,13 +70,17 @@ public class HearingDevicesDialogManager { mDialog = mDialogFactory.create(!isAnyBondedHearingDevice()).createDialog(); - if (view != null) { - mDialogTransitionAnimator.showFromView(mDialog, view, + if (expandable != null) { + DialogTransitionAnimator.Controller controller = expandable.dialogTransitionController( new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG), /* animateBackgroundBoundsChange= */ true); - } else { - mDialog.show(); + INTERACTION_JANK_TAG)); + if (controller != null) { + mDialogTransitionAnimator.show(mDialog, + controller, /* animateBackgroundBoundsChange= */ true); + return; + } } + mDialog.show(); } private void destroyDialog() { 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 99be7628b3c6..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,14 @@ 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 +import com.android.systemui.qs.tiles.impl.onehanded.ui.OneHandedModeTileMapper import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileDataInteractor import com.android.systemui.qs.tiles.impl.reducebrightness.domain.interactor.ReduceBrightColorsTileUserActionInteractor import com.android.systemui.qs.tiles.impl.reducebrightness.domain.model.ReduceBrightColorsTileModel @@ -113,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 @@ -256,5 +265,60 @@ interface QSAccessibilityModule { ), instanceId = uiEventLogger.getNewInstanceId(), ) + + /** Inject One Handed Mode Tile into tileViewModelMap in QSModule. */ + @Provides + @IntoMap + @StringKey(ONE_HANDED_TILE_SPEC) + fun provideOneHandedModeTileViewModel( + factory: QSTileViewModelFactory.Static<OneHandedModeTileModel>, + mapper: OneHandedModeTileMapper, + stateInteractor: OneHandedModeTileDataInteractor, + userActionInteractor: OneHandedModeTileUserActionInteractor + ): QSTileViewModel = + if (Flags.qsNewTilesFuture()) + factory.create( + TileSpec.create(ONE_HANDED_TILE_SPEC), + userActionInteractor, + stateInteractor, + 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/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java index 9c7fc9dd307f..9ef9938ab8ad 100644 --- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java @@ -23,8 +23,7 @@ import android.graphics.Region; import android.view.GestureDetector; import android.view.MotionEvent; -import androidx.annotation.NonNull; - +import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.phone.CentralSurfaces; import java.util.Optional; @@ -38,34 +37,29 @@ import javax.inject.Named; */ public class ShadeTouchHandler implements TouchHandler { private final Optional<CentralSurfaces> mSurfaces; + private final ShadeViewController mShadeViewController; private final int mInitiationHeight; - /** - * Tracks whether or not we are capturing a given touch. Will be null before and after a touch. - */ - private Boolean mCapture; - @Inject ShadeTouchHandler(Optional<CentralSurfaces> centralSurfaces, + ShadeViewController shadeViewController, @Named(NOTIFICATION_SHADE_GESTURE_INITIATION_HEIGHT) int initiationHeight) { mSurfaces = centralSurfaces; + mShadeViewController = shadeViewController; mInitiationHeight = initiationHeight; } @Override public void onSessionStart(TouchSession session) { - if (mSurfaces.isEmpty()) { + if (mSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) { session.pop(); return; } - session.registerCallback(() -> mCapture = null); - session.registerInputListener(ev -> { + mShadeViewController.handleExternalTouch((MotionEvent) ev); + if (ev instanceof MotionEvent) { - if (mCapture != null && mCapture) { - mSurfaces.get().handleExternalShadeWindowTouch((MotionEvent) ev); - } if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) { session.pop(); } @@ -74,25 +68,15 @@ public class ShadeTouchHandler implements TouchHandler { session.registerGestureListener(new GestureDetector.SimpleOnGestureListener() { @Override - public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (mCapture == null) { - // Only capture swipes that are going downwards. - mCapture = Math.abs(distanceY) > Math.abs(distanceX) && distanceY < 0; - if (mCapture) { - // Send the initial touches over, as the input listener has already - // processed these touches. - mSurfaces.get().handleExternalShadeWindowTouch(e1); - mSurfaces.get().handleExternalShadeWindowTouch(e2); - } - } - return mCapture; + return true; } @Override - public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return mCapture; + return true; } }); } diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 5df7fc9865ff..fcba425f0956 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.authentication.domain.interactor +import android.app.admin.flags.Flags import android.os.UserHandle import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView @@ -288,9 +289,15 @@ constructor( private suspend fun getWipeTarget(): WipeTarget { // Check which profile has the strictest policy for failed authentication attempts. val userToBeWiped = repository.getProfileWithMinFailedUnlockAttemptsForWipe() + val primaryUser = + if (Flags.headlessSingleUserFixes()) { + selectedUserInteractor.getMainUserId() ?: UserHandle.USER_SYSTEM + } else { + UserHandle.USER_SYSTEM + } return when (userToBeWiped) { selectedUserInteractor.getSelectedUserId() -> - if (userToBeWiped == UserHandle.USER_SYSTEM) { + if (userToBeWiped == primaryUser) { WipeTarget.WholeDevice } else { WipeTarget.User diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 5ba0b2df7664..b75b292be597 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, + getRequestId(), 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,13 +429,14 @@ 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(); + mPromptSelectorInteractorProvider.get().resetPrompt(getRequestId()); } } @@ -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: " @@ -900,7 +884,8 @@ public class AuthContainerView extends LinearLayout final Runnable endActionRunnable = () -> { setVisibility(View.INVISIBLE); if (Flags.customBiometricPrompt() && constraintBp()) { - mPromptSelectorInteractorProvider.get().resetPrompt(); + // TODO(b/288175645): resetPrompt calls should be lifecycle aware + mPromptSelectorInteractorProvider.get().resetPrompt(getRequestId()); } removeWindowIfAttached(); }; 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/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 9816896e3ea8..298b87d05f39 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -32,11 +32,18 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState @@ -131,6 +138,7 @@ open class UdfpsKeyguardViewControllerLegacy( override fun onUnlockedChanged() { updatePauseAuth() } + override fun onLaunchTransitionFadingAwayChanged() { launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway updatePauseAuth() @@ -211,7 +219,10 @@ open class UdfpsKeyguardViewControllerLegacy( suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job { return scope.launch { transitionInteractor - .transition(KeyguardState.PRIMARY_BOUNCER, KeyguardState.AOD) + .transition( + edge = Edge.create(Scenes.Bouncer, AOD), + edgeWithoutSceneContainer = Edge.create(PRIMARY_BOUNCER, AOD) + ) .collect { transitionStep -> view.onDozeAmountChanged( transitionStep.value, @@ -225,8 +236,7 @@ open class UdfpsKeyguardViewControllerLegacy( @VisibleForTesting suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect { - transitionStep -> + transitionInteractor.transition(Edge.create(DREAMING, AOD)).collect { transitionStep -> view.onDozeAmountChanged( transitionStep.value, transitionStep.value, @@ -239,23 +249,21 @@ open class UdfpsKeyguardViewControllerLegacy( @VisibleForTesting suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor - .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD) - .collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, - ) - } + transitionInteractor.transition(Edge.create(ALTERNATE_BOUNCER, AOD)).collect { + transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, + ) + } } } @VisibleForTesting suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect { - transitionStep -> + transitionInteractor.transition(Edge.create(AOD, OCCLUDED)).collect { transitionStep -> view.onDozeAmountChanged( 1f - transitionStep.value, 1f - transitionStep.value, @@ -268,8 +276,7 @@ open class UdfpsKeyguardViewControllerLegacy( @VisibleForTesting suspend fun listenForOccludedToAodTransition(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor.transition(KeyguardState.OCCLUDED, KeyguardState.AOD).collect { - transitionStep -> + transitionInteractor.transition(Edge.create(OCCLUDED, AOD)).collect { transitionStep -> view.onDozeAmountChanged( transitionStep.value, transitionStep.value, @@ -282,14 +289,18 @@ open class UdfpsKeyguardViewControllerLegacy( @VisibleForTesting suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job { return scope.launch { - transitionInteractor.transition(KeyguardState.GONE, KeyguardState.AOD).collect { - transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, + transitionInteractor + .transition( + edge = Edge.create(Scenes.Gone, AOD), + edgeWithoutSceneContainer = Edge.create(GONE, AOD) ) - } + .collect { transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + ANIMATE_APPEAR_ON_SCREEN_OFF, + ) + } } } @@ -298,13 +309,10 @@ open class UdfpsKeyguardViewControllerLegacy( return scope.launch { transitionInteractor.dozeAmountTransition.collect { transitionStep -> if ( - transitionStep.from == KeyguardState.AOD && + transitionStep.from == AOD && transitionStep.transitionState == TransitionState.CANCELED ) { - if ( - transitionInteractor.startedKeyguardTransitionStep.first().to != - KeyguardState.AOD - ) { + if (transitionInteractor.startedKeyguardTransitionStep.first().to != AOD) { // If the next started transition isn't transitioning back to AOD, force // doze amount to be 0f (as if the transition to the lockscreen completed). view.onDozeAmountChanged( @@ -557,6 +565,7 @@ open class UdfpsKeyguardViewControllerLegacy( private fun updateScaleFactor() { udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) } } + companion object { const val TAG = "UdfpsKeyguardViewController" } 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/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt index cc524840947b..ca03a00cca0c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt @@ -34,6 +34,7 @@ import android.hardware.biometrics.events.AuthenticationStoppedInfo import android.hardware.biometrics.events.AuthenticationSucceededInfo import android.hardware.face.FaceManager import android.hardware.fingerprint.FingerprintManager +import android.util.Log import com.android.systemui.biometrics.shared.model.AuthenticationReason import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations import com.android.systemui.biometrics.shared.model.AuthenticationState @@ -52,6 +53,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn /** A repository for the state of biometric authentication. */ @@ -85,6 +87,7 @@ constructor( private val authenticationState: Flow<AuthenticationState> = conflatedCallbackFlow { val updateAuthenticationState = { state: AuthenticationState -> + Log.d(TAG, "authenticationState updated: $state") trySendWithFailureLogging(state, TAG, "Error sending AuthenticationState state") } @@ -187,6 +190,7 @@ constructor( it.biometricSourceType == BiometricSourceType.FINGERPRINT) } .map { it.requestReason } + .onEach { Log.d(TAG, "fingerprintAuthenticationReason updated: $it") } override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> = authenticationState diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt index 40d38dd83154..ba51d02fd288 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt @@ -41,7 +41,10 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -52,7 +55,7 @@ import kotlinx.coroutines.withContext */ interface FingerprintPropertyRepository { /** Whether the fingerprint properties have been initialized yet. */ - val propertiesInitialized: StateFlow<Boolean> + val propertiesInitialized: Flow<Boolean> /** The id of fingerprint sensor. */ val sensorId: Flow<Int> @@ -110,15 +113,6 @@ constructor( initialValue = UNINITIALIZED_PROPS, ) - override val propertiesInitialized: StateFlow<Boolean> = - props - .map { it != UNINITIALIZED_PROPS } - .stateIn( - applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = props.value != UNINITIALIZED_PROPS, - ) - override val sensorId: Flow<Int> = props.map { it.sensorId } override val strength: Flow<SensorStrength> = props.map { it.sensorStrength.toSensorStrength() } @@ -139,6 +133,22 @@ constructor( } } + override val propertiesInitialized: Flow<Boolean> = + combine( + props + .map { it != UNINITIALIZED_PROPS } + .onStart { emit(props.value != UNINITIALIZED_PROPS) }, + sensorId.map {}.onStart { if (props.value != UNINITIALIZED_PROPS) emit(Unit) }, + sensorLocations + .map {} + .onStart { if (props.value != UNINITIALIZED_PROPS) emit(Unit) }, + sensorType.map {}.onStart { if (props.value != UNINITIALIZED_PROPS) emit(Unit) }, + strength.map {}.onStart { if (props.value != UNINITIALIZED_PROPS) emit(Unit) }, + ) { initialized, _, _, _, _ -> + initialized + } + .distinctUntilChanged() + companion object { private const val TAG = "FingerprintPropertyRepositoryImpl" private val UNINITIALIZED_PROPS = 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..230b30bc548e 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,9 @@ package com.android.systemui.biometrics.data.repository -import android.hardware.biometrics.Flags import android.hardware.biometrics.PromptInfo -import com.android.systemui.Flags.constraintBp +import android.util.Log 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 @@ -53,11 +50,14 @@ interface PromptRepository { /** The user that the prompt is for. */ val userId: StateFlow<Int?> + /** The request that the prompt is for. */ + val requestId: StateFlow<Long?> + /** 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,29 +69,18 @@ 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, userId: Int, + requestId: Long, gatekeeperChallenge: Long?, kind: PromptKind, opPackageName: String, ) /** Unset the prompt info. */ - fun unsetPrompt() + fun unsetPrompt(requestId: Long) } @SysUISingleton @@ -125,8 +114,11 @@ 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 _requestId: MutableStateFlow<Long?> = MutableStateFlow(null) + override val requestId = _requestId.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,41 +137,33 @@ 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, + requestId: Long, gatekeeperChallenge: Long?, kind: PromptKind, opPackageName: String, ) { - _kind.value = kind + _promptKind.value = kind _userId.value = userId + _requestId.value = requestId _challenge.value = gatekeeperChallenge _promptInfo.value = promptInfo _opPackageName.value = opPackageName } - override fun unsetPrompt() { - _promptInfo.value = null - _userId.value = null - _challenge.value = null - _kind.value = PromptKind.Biometric() - _opPackageName.value = null + override fun unsetPrompt(requestId: Long) { + if (requestId == _requestId.value) { + _promptInfo.value = null + _userId.value = null + _requestId.value = null + _challenge.value = null + _promptKind.value = PromptKind.None + _opPackageName.value = null + } else { + Log.w(TAG, "Ignoring unsetPrompt - requestId mismatch") + } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt index 6e79e4693728..83aefcaac36d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.domain.interactor import android.app.ActivityTaskManager +import android.util.Log import com.android.systemui.biometrics.data.repository.BiometricStatusRepository import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.AuthenticationReason @@ -26,6 +27,7 @@ import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.onEach /** Encapsulates business logic for interacting with biometric authentication state. */ interface BiometricStatusInteractor { @@ -49,15 +51,20 @@ constructor( override val sfpsAuthenticationReason: Flow<AuthenticationReason> = combine( - biometricStatusRepository.fingerprintAuthenticationReason, - fingerprintPropertyRepository.sensorType - ) { reason: AuthenticationReason, sensorType -> - if (sensorType.isPowerButton() && reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)) { - reason - } else { - AuthenticationReason.NotRunning + biometricStatusRepository.fingerprintAuthenticationReason, + fingerprintPropertyRepository.sensorType + ) { reason: AuthenticationReason, sensorType -> + if ( + sensorType.isPowerButton() && + reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager) + ) { + reason + } else { + AuthenticationReason.NotRunning + } } - }.distinctUntilChanged() + .distinctUntilChanged() + .onEach { Log.d(TAG, "sfpsAuthenticationReason updated: $it") } override val fingerprintAcquiredStatus: Flow<FingerprintAuthenticationStatus> = biometricStatusRepository.fingerprintAcquiredStatus diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt index 3112b673d724..a74b0b07299c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt @@ -46,13 +46,13 @@ constructor( displayStateInteractor: DisplayStateInteractor, udfpsOverlayInteractor: UdfpsOverlayInteractor, ) { - val propertiesInitialized: StateFlow<Boolean> = repository.propertiesInitialized + val propertiesInitialized: Flow<Boolean> = repository.propertiesInitialized val isUdfps: StateFlow<Boolean> = repository.sensorType .map { it.isUdfps() } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = repository.sensorType.value.isUdfps(), ) 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..dc338d07f9e7 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,38 +76,25 @@ 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, + requestId: Long, 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. */ - fun resetPrompt() + fun resetPrompt(requestId: Long) } @SysUISingleton @@ -111,7 +103,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 +111,7 @@ constructor( promptRepository.promptInfo, promptRepository.challenge, promptRepository.userId, - promptRepository.kind, + promptRepository.promptKind, promptRepository.opPackageName, ) { promptInfo, challenge, userId, kind, opPackageName -> if ( @@ -141,6 +133,8 @@ constructor( } } + override val promptKind: StateFlow<PromptKind> = promptRepository.promptKind + override val isConfirmationRequired: Flow<Boolean> = promptRepository.isConfirmationRequired.distinctUntilChanged() @@ -152,70 +146,69 @@ 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!!, + promptRepository.requestId.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, + requestId: Long, + 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, + requestId = requestId, gatekeeperChallenge = challenge, - kind = kind.asBiometricPromptCredential(), + kind = kind, opPackageName = opPackageName, ) } - override fun resetPrompt() { - promptRepository.unsetPrompt() + override fun resetPrompt(requestId: Long) { + promptRepository.unsetPrompt(requestId) } } - -// 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..47174c006735 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) @@ -198,29 +199,32 @@ object BiometricViewSizeBinder { iconParams.leftMargin = position.left mediumConstraintSet.clear( R.id.biometric_icon, - ConstraintSet.END + ConstraintSet.RIGHT ) mediumConstraintSet.connect( R.id.biometric_icon, - ConstraintSet.START, + ConstraintSet.LEFT, ConstraintSet.PARENT_ID, - ConstraintSet.START + ConstraintSet.LEFT ) mediumConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.START, + ConstraintSet.LEFT, position.left ) - smallConstraintSet.clear(R.id.biometric_icon, ConstraintSet.END) + smallConstraintSet.clear( + R.id.biometric_icon, + ConstraintSet.RIGHT + ) smallConstraintSet.connect( R.id.biometric_icon, - ConstraintSet.START, + ConstraintSet.LEFT, ConstraintSet.PARENT_ID, - ConstraintSet.START + ConstraintSet.LEFT ) smallConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.START, + ConstraintSet.LEFT, position.left ) } @@ -251,32 +255,32 @@ object BiometricViewSizeBinder { iconParams.rightMargin = position.right mediumConstraintSet.clear( R.id.biometric_icon, - ConstraintSet.START + ConstraintSet.LEFT ) mediumConstraintSet.connect( R.id.biometric_icon, - ConstraintSet.END, + ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, - ConstraintSet.END + ConstraintSet.RIGHT ) mediumConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.END, + ConstraintSet.RIGHT, position.right ) smallConstraintSet.clear( R.id.biometric_icon, - ConstraintSet.START + ConstraintSet.LEFT ) smallConstraintSet.connect( R.id.biometric_icon, - ConstraintSet.END, + ConstraintSet.RIGHT, ConstraintSet.PARENT_ID, - ConstraintSet.END + ConstraintSet.RIGHT ) smallConstraintSet.setMargin( R.id.biometric_icon, - ConstraintSet.END, + ConstraintSet.RIGHT, position.right ) } @@ -317,6 +321,12 @@ object BiometricViewSizeBinder { lifecycleScope.launch { viewModel.guidelineBounds.collect { bounds -> + val bottomInset = + windowManager.maximumWindowMetrics.windowInsets + .getInsets(WindowInsets.Type.navigationBars()) + .bottom + mediumConstraintSet.setGuidelineEnd(R.id.bottomGuideline, bottomInset) + if (bounds.left >= 0) { mediumConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) smallConstraintSet.setGuidelineBegin(leftGuideline.id, bounds.left) @@ -362,12 +372,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) @@ -378,15 +392,15 @@ object BiometricViewSizeBinder { // Move all content to other panel flipConstraintSet.connect( R.id.scrollView, - ConstraintSet.START, + ConstraintSet.LEFT, R.id.midGuideline, - ConstraintSet.START + ConstraintSet.LEFT ) flipConstraintSet.connect( R.id.scrollView, - ConstraintSet.END, + ConstraintSet.RIGHT, R.id.rightGuideline, - ConstraintSet.END + ConstraintSet.RIGHT ) } @@ -481,7 +495,7 @@ object BiometricViewSizeBinder { v.showContentOrHide(forceHide = size.isSmall) } - if (viewModel.showBpWithoutIconForCredential.value) { + if (viewModel.hideSensorIcon.first()) { iconHolderView.visibility = View.GONE } @@ -492,10 +506,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/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index 4bdbfa272ecc..ff7ac35ba56b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -20,6 +20,7 @@ package com.android.systemui.biometrics.ui.binder import android.content.Context import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.WindowManager @@ -91,6 +92,13 @@ constructor( showIndicatorForDeviceEntry, progressBarIsVisible) = combinedFlows + Log.d( + TAG, + "systemServerAuthReason = $systemServerAuthReason, " + + "showIndicatorForDeviceEntry = " + + "$showIndicatorForDeviceEntry, " + + "progressBarIsVisible = $progressBarIsVisible" + ) if (!isInRearDisplayMode) { if (progressBarIsVisible) { hide() @@ -114,6 +122,10 @@ constructor( /** Show the side fingerprint sensor indicator */ private fun show() { if (overlayView?.isAttachedToWindow == true) { + Log.d( + TAG, + "show(): overlayView $overlayView isAttachedToWindow already, ignoring show request" + ) return } @@ -128,6 +140,7 @@ constructor( ) bind(overlayView!!, overlayViewModel, fpsUnlockTracker.get(), windowManager.get()) overlayView!!.visibility = View.INVISIBLE + Log.d(TAG, "show(): adding overlayView $overlayView") windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams) } @@ -137,6 +150,7 @@ constructor( val lottie = overlayView!!.requireViewById<LottieAnimationView>(R.id.sidefps_animation) lottie.pauseAnimation() lottie.removeAllLottieOnCompositionLoadedListener() + Log.d(TAG, "hide(): removing overlayView $overlayView, setting to null") windowManager.get().removeView(overlayView) overlayView = null } 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/BroadcastDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java index 207f7dbb5816..f320057c0763 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogDelegate.java @@ -221,7 +221,8 @@ public class BroadcastDialogDelegate implements SystemUIDialog.Delegate { (view) -> { // TODO: b/321969740 - Take the userHandle as a parameter and pass it through. // The package name is not sufficient to unambiguously identify an app. - mMediaOutputDialogManager.createAndShow(mOutputPackageName, true, null, null); + mMediaOutputDialogManager.createAndShow( + mOutputPackageName, true, null, null, null); dialog.dismiss(); }); cancelBtn.setOnClickListener((view) -> { 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..911145b62661 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) } } @@ -427,8 +419,7 @@ internal constructor( const val ACTION_PREVIOUSLY_CONNECTED_DEVICE = "com.android.settings.PREVIOUSLY_CONNECTED_DEVICE" const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS" - const val ACTION_AUDIO_SHARING = - "com.google.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS" + const val ACTION_AUDIO_SHARING = "com.android.settings.BLUETOOTH_AUDIO_SHARING_SETTINGS" const val DISABLED_ALPHA = 0.3f const val ENABLED_ALPHA = 1f const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogModule.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogModule.kt new file mode 100644 index 000000000000..2e9169e03d80 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogModule.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.bluetooth.qsdialog + +import com.android.systemui.dagger.SysUISingleton +import dagger.Binds +import dagger.Module + +@Module +interface BluetoothTileDialogModule { + @Binds + @SysUISingleton + fun bindDeviceItemActionInteractor( + impl: DeviceItemActionInteractorImpl + ): DeviceItemActionInteractor +} diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt index eb919e3ca36b..94f465d3c1c3 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt @@ -32,6 +32,7 @@ import com.android.settingslib.bluetooth.BluetoothUtils import com.android.systemui.Prefs import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_AUDIO_SHARING import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE @@ -61,6 +62,7 @@ internal class BluetoothTileDialogViewModel @Inject constructor( private val deviceItemInteractor: DeviceItemInteractor, + private val deviceItemActionInteractor: DeviceItemActionInteractor, private val bluetoothStateInteractor: BluetoothStateInteractor, private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor, private val audioSharingInteractor: AudioSharingInteractor, @@ -82,7 +84,7 @@ constructor( * @param view The view from which the dialog is shown. */ @kotlinx.coroutines.ExperimentalCoroutinesApi - fun showDialog(view: View?) { + fun showDialog(expandable: Expandable?) { cancelJob() job = @@ -93,17 +95,15 @@ constructor( val dialog = dialogDelegate.createDialog() val context = dialog.context - view?.let { - dialogTransitionAnimator.showFromView( - dialog, - it, - animateBackgroundBoundsChange = true, - cuj = - DialogCuj( - InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG - ) + val controller = + expandable?.dialogTransitionController( + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG + ) ) + controller?.let { + dialogTransitionAnimator.show(dialog, it, animateBackgroundBoundsChange = true) } ?: dialog.show() @@ -193,7 +193,7 @@ constructor( // deviceItemClick is emitted when user clicked on a device item. dialogDelegate.deviceItemClick - .onEach { deviceItemInteractor.updateDeviceItemOnClick(it) } + .onEach { deviceItemActionInteractor.onClick(it, dialog) } .launchIn(this) // contentHeight is emitted when the dialog is dismissed. diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt new file mode 100644 index 000000000000..931176003b1b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractor.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bluetooth.qsdialog + +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.phone.SystemUIDialog +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext + +/** Defines interface for click handling of a DeviceItem. */ +interface DeviceItemActionInteractor { + suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) +} + +@SysUISingleton +open class DeviceItemActionInteractorImpl +@Inject +constructor( + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val logger: BluetoothTileDialogLogger, + private val uiEventLogger: UiEventLogger, +) : DeviceItemActionInteractor { + + override suspend fun onClick(deviceItem: DeviceItem, dialog: SystemUIDialog) { + withContext(backgroundDispatcher) { + logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type) + + deviceItem.cachedBluetoothDevice.apply { + when (deviceItem.type) { + DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> { + disconnect() + uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT) + } + DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> { + uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED) + } + DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { + setActive() + uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE) + } + DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> { + disconnect() + uiEventLogger.log( + BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT + ) + } + DeviceItemType.SAVED_BLUETOOTH_DEVICE -> { + connect() + uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt index 66e593b94b21..1526cd9675c7 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt @@ -20,7 +20,6 @@ import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice import android.content.Context import android.media.AudioManager -import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.BluetoothCallback import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager @@ -52,7 +51,6 @@ constructor( private val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter(), private val localBluetoothManager: LocalBluetoothManager?, private val systemClock: SystemClock, - private val uiEventLogger: UiEventLogger, private val logger: BluetoothTileDialogLogger, @Application private val coroutineScope: CoroutineScope, @Background private val backgroundDispatcher: CoroutineDispatcher, @@ -169,38 +167,6 @@ constructor( ) } - internal suspend fun updateDeviceItemOnClick(deviceItem: DeviceItem) { - withContext(backgroundDispatcher) { - logger.logDeviceClick(deviceItem.cachedBluetoothDevice.address, deviceItem.type) - - deviceItem.cachedBluetoothDevice.apply { - when (deviceItem.type) { - DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE -> { - disconnect() - uiEventLogger.log(BluetoothTileDialogUiEvent.ACTIVE_DEVICE_DISCONNECT) - } - DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE -> { - uiEventLogger.log(BluetoothTileDialogUiEvent.AUDIO_SHARING_DEVICE_CLICKED) - } - DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE -> { - setActive() - uiEventLogger.log(BluetoothTileDialogUiEvent.CONNECTED_DEVICE_SET_ACTIVE) - } - DeviceItemType.CONNECTED_BLUETOOTH_DEVICE -> { - disconnect() - uiEventLogger.log( - BluetoothTileDialogUiEvent.CONNECTED_OTHER_DEVICE_DISCONNECT - ) - } - DeviceItemType.SAVED_BLUETOOTH_DEVICE -> { - connect() - uiEventLogger.log(BluetoothTileDialogUiEvent.SAVED_DEVICE_CONNECT) - } - } - } - } - } - internal fun setDeviceItemFactoryListForTesting(list: List<DeviceItemFactory>) { deviceItemFactoryList = list } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt index 053482431bcb..f1c3f949ffba 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt @@ -323,6 +323,9 @@ constructor( alternateBouncerUIAvailable .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false) .launchIn(applicationScope) + alternateBouncerVisible + .logDiffsForTable(buffer, "", "AlternateBouncerVisible", false) + .launchIn(applicationScope) lastShownSecurityMode .map { it.name } .logDiffsForTable(buffer, "", "lastShownSecurityMode", null) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt index fa19bf478453..e0334a060ee2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt @@ -29,7 +29,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.kotlin.BooleanFlowOperators.or +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.time.SystemClock import dagger.Lazy import javax.inject.Inject @@ -78,7 +78,7 @@ constructor( bouncerRepository.alternateBouncerUIAvailable } private val isDozingOrAod: Flow<Boolean> = - or( + anyOf( keyguardTransitionInteractor.get().transitionValue(KeyguardState.DOZING).map { it > 0f }, diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt index c018ecb25835..0544a4f66295 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/BrightnessPolicyRepository.kt @@ -18,6 +18,8 @@ package com.android.systemui.brightness.data.repository import android.content.Context import android.os.UserManager +import com.android.settingslib.RestrictedLockUtils +import com.android.systemui.Flags.enforceBrightnessBaseUserRestriction import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -66,7 +68,18 @@ constructor( user.id ) ?.let { PolicyRestriction.Restricted(it) } - ?: PolicyRestriction.NoRestriction + ?: if ( + enforceBrightnessBaseUserRestriction() && + userRestrictionChecker.hasBaseUserRestriction( + applicationContext, + UserManager.DISALLOW_CONFIG_BRIGHTNESS, + user.id + ) + ) { + PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin()) + } else { + PolicyRestriction.NoRestriction + } } .flowOn(backgroundDispatcher) } diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt index c1be37af3aeb..a51d8ff4faa5 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.material3.MaterialTheme 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 @@ -32,6 +31,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.PlatformSlider import com.android.systemui.brightness.shared.GammaBrightness import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel @@ -107,10 +107,13 @@ fun BrightnessSliderContainer( viewModel: BrightnessSliderViewModel, modifier: Modifier = Modifier, ) { - val gamma: Int by viewModel.currentBrightness.map { it.value }.collectAsState(initial = 0) + val gamma: Int by + viewModel.currentBrightness.map { it.value }.collectAsStateWithLifecycle(initialValue = 0) val coroutineScope = rememberCoroutineScope() val restriction by - viewModel.policyRestriction.collectAsState(initial = PolicyRestriction.NoRestriction) + viewModel.policyRestriction.collectAsStateWithLifecycle( + initialValue = PolicyRestriction.NoRestriction + ) BrightnessSlider( gammaValue = gamma, diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java index beaa170943fd..2eca02c2b0d1 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java @@ -31,9 +31,12 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.dock.DockManager; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.BatteryController; @@ -88,6 +91,8 @@ class FalsingCollectorImpl implements FalsingCollector { private final JavaAdapter mJavaAdapter; private final SystemClock mSystemClock; private final Lazy<SelectedUserInteractor> mUserInteractor; + private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor; + private final Lazy<SceneContainerOcclusionInteractor> mSceneContainerOcclusionInteractor; private int mState; private boolean mShowingAod; @@ -170,7 +175,9 @@ class FalsingCollectorImpl implements FalsingCollector { JavaAdapter javaAdapter, SystemClock systemClock, Lazy<SelectedUserInteractor> userInteractor, - Lazy<CommunalInteractor> communalInteractorLazy) { + Lazy<CommunalInteractor> communalInteractorLazy, + Lazy<DeviceEntryInteractor> deviceEntryInteractor, + Lazy<SceneContainerOcclusionInteractor> sceneContainerOcclusionInteractor) { mFalsingDataProvider = falsingDataProvider; mFalsingManager = falsingManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -186,6 +193,8 @@ class FalsingCollectorImpl implements FalsingCollector { mSystemClock = systemClock; mUserInteractor = userInteractor; mCommunalInteractorLazy = communalInteractorLazy; + mDeviceEntryInteractor = deviceEntryInteractor; + mSceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor; } @Override @@ -196,7 +205,18 @@ class FalsingCollectorImpl implements FalsingCollector { mStatusBarStateController.addCallback(mStatusBarStateListener); mState = mStatusBarStateController.getState(); - mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); + if (SceneContainerFlag.isEnabled()) { + mJavaAdapter.alwaysCollectFlow( + mDeviceEntryInteractor.get().isDeviceEntered(), + this::isDeviceEnteredChanged + ); + mJavaAdapter.alwaysCollectFlow( + mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion(), + this::isInvisibleDueToOcclusionChanged + ); + } else { + mKeyguardStateController.addCallback(mKeyguardStateControllerCallback); + } mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback); @@ -206,7 +226,7 @@ class FalsingCollectorImpl implements FalsingCollector { ); final CommunalInteractor communalInteractor = mCommunalInteractorLazy.get(); mJavaAdapter.alwaysCollectFlow( - BooleanFlowOperators.INSTANCE.and( + BooleanFlowOperators.INSTANCE.allOf( communalInteractor.isCommunalEnabled(), communalInteractor.isCommunalShowing()), this::onShowingCommunalHubChanged @@ -216,6 +236,14 @@ class FalsingCollectorImpl implements FalsingCollector { mDockManager.addListener(mDockEventListener); } + public void isDeviceEnteredChanged(boolean unused) { + updateSensorRegistration(); + } + + public void isInvisibleDueToOcclusionChanged(boolean unused) { + updateSensorRegistration(); + } + @Override public void onSuccessfulUnlock() { logDebug("REAL: onSuccessfulUnlock"); @@ -292,6 +320,7 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onKeyEvent(KeyEvent ev) { + logDebug("REAL: onKeyEvent(" + KeyEvent.actionToString(ev.getAction()) + ")"); // Only collect if it is an ACTION_UP action and is allow-listed if (ev.getAction() == KeyEvent.ACTION_UP && mAcceptedKeycodes.contains(ev.getKeyCode())) { mFalsingDataProvider.onKeyEvent(ev); @@ -300,8 +329,8 @@ class FalsingCollectorImpl implements FalsingCollector { @Override public void onTouchEvent(MotionEvent ev) { - logDebug("REAL: onTouchEvent(" + ev.getActionMasked() + ")"); - if (!mKeyguardStateController.isShowing()) { + logDebug("REAL: onTouchEvent(" + MotionEvent.actionToString(ev.getActionMasked()) + ")"); + if (!isKeyguardShowing()) { avoidGesture(); return; } @@ -398,12 +427,13 @@ class FalsingCollectorImpl implements FalsingCollector { } private boolean shouldBeRegisteredToSensors() { - return mScreenOn - && (mState == StatusBarState.KEYGUARD - || (mState == StatusBarState.SHADE - && mKeyguardStateController.isOccluded() - && mKeyguardStateController.isShowing())) - && !mShowingAod; + final boolean isKeyguard = mState == StatusBarState.KEYGUARD; + + final boolean isShadeOverOccludedKeyguard = mState == StatusBarState.SHADE + && isKeyguardShowing() + && isKeyguardOccluded(); + + return mScreenOn && !mShowingAod && (isKeyguard || isShadeOverOccludedKeyguard); } private void updateSensorRegistration() { @@ -445,6 +475,32 @@ class FalsingCollectorImpl implements FalsingCollector { mFalsingManager.onProximityEvent(new ProximityEventImpl(proximityEvent)); } + /** + * Returns {@code true} if the keyguard is showing (whether or not the screen is on, whether or + * not an activity is occluding the keyguard, and whether or not the shade is open on top of the + * keyguard), or {@code false} if the user has dismissed the keyguard by authenticating or + * swiping up. + */ + private boolean isKeyguardShowing() { + if (SceneContainerFlag.isEnabled()) { + return !mDeviceEntryInteractor.get().isDeviceEntered().getValue(); + } else { + return mKeyguardStateController.isShowing(); + } + } + + /** + * Returns {@code true} if there is an activity display on top of ("occluding") the keyguard, or + * {@code false} if an activity is not occluding the keyguard (including if the keyguard is not + * showing at all). + */ + private boolean isKeyguardOccluded() { + if (SceneContainerFlag.isEnabled()) { + return mSceneContainerOcclusionInteractor.get().getInvisibleDueToOcclusion().getValue(); + } else { + return mKeyguardStateController.isOccluded(); + } + } static void logDebug(String msg) { if (DEBUG) { diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt index b289fa49d06c..6b22137e455e 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorNoOp.kt @@ -61,11 +61,11 @@ class FalsingCollectorNoOp @Inject constructor() : FalsingCollector { } override fun onKeyEvent(ev: KeyEvent) { - logDebug("NOOP: onKeyEvent(${ev.action}") + logDebug("NOOP: onKeyEvent(${KeyEvent.actionToString(ev.action)}") } override fun onTouchEvent(ev: MotionEvent) { - logDebug("NOOP: onTouchEvent(${ev.actionMasked})") + logDebug("NOOP: onTouchEvent(${MotionEvent.actionToString(ev.actionMasked)})") } override fun onMotionEventComplete() { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java index b2699673f7ea..ba236ba016ff 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java @@ -18,6 +18,8 @@ package com.android.systemui.clipboardoverlay; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static com.android.systemui.Flags.screenshotShelfUi2; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -25,6 +27,7 @@ import android.animation.ObjectAnimator; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.Nullable; +import android.app.PendingIntent; import android.app.RemoteAction; import android.content.Context; import android.content.res.Resources; @@ -36,6 +39,7 @@ import android.graphics.Region; import android.graphics.drawable.Icon; import android.util.AttributeSet; import android.util.DisplayMetrics; +import android.util.Log; import android.util.MathUtils; import android.util.TypedValue; import android.view.DisplayCutout; @@ -58,6 +62,12 @@ import com.android.systemui.res.R; import com.android.systemui.screenshot.DraggableConstraintLayout; import com.android.systemui.screenshot.FloatingWindowUtil; import com.android.systemui.screenshot.OverlayActionChip; +import com.android.systemui.screenshot.ui.binder.ActionButtonViewBinder; +import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance; +import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel; + +import kotlin.Unit; +import kotlin.jvm.functions.Function0; import java.util.ArrayList; @@ -85,7 +95,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { private final DisplayMetrics mDisplayMetrics; private final AccessibilityManager mAccessibilityManager; - private final ArrayList<OverlayActionChip> mActionChips = new ArrayList<>(); + private final ArrayList<View> mActionChips = new ArrayList<>(); private View mClipboardPreview; private ImageView mImagePreview; @@ -93,11 +103,13 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { private TextView mHiddenPreview; private LinearLayout mMinimizedPreview; private View mPreviewBorder; - private OverlayActionChip mShareChip; - private OverlayActionChip mRemoteCopyChip; + private View mShareChip; + private View mRemoteCopyChip; private View mActionContainerBackground; private View mDismissButton; private LinearLayout mActionContainer; + private ClipboardOverlayCallbacks mClipboardCallbacks; + private ActionButtonViewBinder mActionButtonViewBinder = new ActionButtonViewBinder(); public ClipboardOverlayView(Context context) { this(context, null); @@ -128,17 +140,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { mRemoteCopyChip = requireViewById(R.id.remote_copy_chip); mDismissButton = requireViewById(R.id.dismiss_button); - mShareChip.setAlpha(1); - mRemoteCopyChip.setAlpha(1); - mShareChip.setContentDescription(mContext.getString(com.android.internal.R.string.share)); - - mRemoteCopyChip.setIcon( - Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24), true); - mShareChip.setIcon( - Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true); - - mRemoteCopyChip.setContentDescription( - mContext.getString(R.string.clipboard_send_nearby_description)); + bindDefaultActionChips(); mTextPreview.getViewTreeObserver().addOnPreDrawListener(() -> { int availableHeight = mTextPreview.getHeight() @@ -149,15 +151,71 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { super.onFinishInflate(); } + private void bindDefaultActionChips() { + if (screenshotShelfUi2()) { + mActionButtonViewBinder.bind(mRemoteCopyChip, + ActionButtonViewModel.Companion.withNextId( + new ActionButtonAppearance( + Icon.createWithResource(mContext, + R.drawable.ic_baseline_devices_24).loadDrawable( + mContext), + null, + mContext.getString(R.string.clipboard_send_nearby_description), + true), + new Function0<>() { + @Override + public Unit invoke() { + if (mClipboardCallbacks != null) { + mClipboardCallbacks.onRemoteCopyButtonTapped(); + } + return null; + } + })); + mActionButtonViewBinder.bind(mShareChip, + ActionButtonViewModel.Companion.withNextId( + new ActionButtonAppearance( + Icon.createWithResource(mContext, + R.drawable.ic_screenshot_share).loadDrawable(mContext), + null, + mContext.getString(com.android.internal.R.string.share), + true), + new Function0<>() { + @Override + public Unit invoke() { + if (mClipboardCallbacks != null) { + mClipboardCallbacks.onShareButtonTapped(); + } + return null; + } + })); + } else { + mShareChip.setAlpha(1); + mRemoteCopyChip.setAlpha(1); + + ((ImageView) mRemoteCopyChip.findViewById(R.id.overlay_action_chip_icon)).setImageIcon( + Icon.createWithResource(mContext, R.drawable.ic_baseline_devices_24)); + ((ImageView) mShareChip.findViewById(R.id.overlay_action_chip_icon)).setImageIcon( + Icon.createWithResource(mContext, R.drawable.ic_screenshot_share)); + + mShareChip.setContentDescription( + mContext.getString(com.android.internal.R.string.share)); + mRemoteCopyChip.setContentDescription( + mContext.getString(R.string.clipboard_send_nearby_description)); + } + } + @Override public void setCallbacks(SwipeDismissCallbacks callbacks) { super.setCallbacks(callbacks); ClipboardOverlayCallbacks clipboardCallbacks = (ClipboardOverlayCallbacks) callbacks; - mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped()); + if (!screenshotShelfUi2()) { + mShareChip.setOnClickListener(v -> clipboardCallbacks.onShareButtonTapped()); + mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped()); + } mDismissButton.setOnClickListener(v -> clipboardCallbacks.onDismissButtonTapped()); - mRemoteCopyChip.setOnClickListener(v -> clipboardCallbacks.onRemoteCopyButtonTapped()); mClipboardPreview.setOnClickListener(v -> clipboardCallbacks.onPreviewTapped()); mMinimizedPreview.setOnClickListener(v -> clipboardCallbacks.onMinimizedViewTapped()); + mClipboardCallbacks = clipboardCallbacks; } void setEditAccessibilityAction(boolean editable) { @@ -285,7 +343,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { } void resetActionChips() { - for (OverlayActionChip chip : mActionChips) { + for (View chip : mActionChips) { mActionContainer.removeView(chip); } mActionChips.clear(); @@ -437,7 +495,12 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { void setActionChip(RemoteAction action, Runnable onFinish) { mActionContainerBackground.setVisibility(View.VISIBLE); - OverlayActionChip chip = constructActionChip(action, onFinish); + View chip; + if (screenshotShelfUi2()) { + chip = constructShelfActionChip(action, onFinish); + } else { + chip = constructActionChip(action, onFinish); + } mActionContainer.addView(chip); mActionChips.add(chip); } @@ -450,6 +513,27 @@ public class ClipboardOverlayView extends DraggableConstraintLayout { v.setVisibility(View.VISIBLE); } + private View constructShelfActionChip(RemoteAction action, Runnable onFinish) { + View chip = LayoutInflater.from(mContext).inflate( + R.layout.shelf_action_chip, mActionContainer, false); + mActionButtonViewBinder.bind(chip, ActionButtonViewModel.Companion.withNextId( + new ActionButtonAppearance(action.getIcon().loadDrawable(mContext), + action.getTitle(), action.getTitle(), false), new Function0<>() { + @Override + public Unit invoke() { + try { + action.getActionIntent().send(); + onFinish.run(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Failed to send intent"); + } + return null; + } + })); + + return chip; + } + private OverlayActionChip constructActionChip(RemoteAction action, Runnable onFinish) { OverlayActionChip chip = (OverlayActionChip) LayoutInflater.from(mContext).inflate( R.layout.overlay_action_chip, mActionContainer, false); diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java index ff9fba4c03f1..740a93eb081c 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java @@ -18,6 +18,8 @@ package com.android.systemui.clipboardoverlay.dagger; import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT; +import static com.android.systemui.Flags.screenshotShelfUi2; + import static java.lang.annotation.RetentionPolicy.RUNTIME; import android.content.Context; @@ -57,8 +59,13 @@ public interface ClipboardOverlayModule { */ @Provides static ClipboardOverlayView provideClipboardOverlayView(@OverlayWindowContext Context context) { - return (ClipboardOverlayView) LayoutInflater.from(context).inflate( - R.layout.clipboard_overlay, null); + if (screenshotShelfUi2()) { + return (ClipboardOverlayView) LayoutInflater.from(context).inflate( + R.layout.clipboard_overlay2, null); + } else { + return (ClipboardOverlayView) LayoutInflater.from(context).inflate( + R.layout.clipboard_overlay, null); + } } @Qualifier 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/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt index 07814512b4b8..85e2bdb43ba5 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.DisposableHandle class LongPressHandlingView( context: Context, attrs: AttributeSet?, - private val longPressDuration: () -> Long, + longPressDuration: () -> Long, ) : View( context, @@ -89,6 +89,12 @@ class LongPressHandlingView( ) } + var longPressDuration: () -> Long + get() = interactionHandler.longPressDuration + set(longPressDuration) { + interactionHandler.longPressDuration = longPressDuration + } + fun setLongPressHandlingEnabled(isEnabled: Boolean) { interactionHandler.isLongPressHandlingEnabled = isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt index a742e8d614b1..d3fc610bc52e 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingViewInteractionHandler.kt @@ -34,7 +34,7 @@ class LongPressHandlingViewInteractionHandler( /** Callback reporting the a single tap gesture was detected at the given coordinates. */ private val onSingleTapDetected: () -> Unit, /** Time for the touch to be considered a long-press in ms */ - private val longPressDuration: () -> Long, + var longPressDuration: () -> Long, ) { sealed class MotionEventModel { object Other : MotionEventModel() 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/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index f437032d0ddb..6f20a8daf00a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -17,7 +17,6 @@ package com.android.systemui.communal import android.provider.Settings -import android.service.dreams.Flags.dreamTracksFocus import com.android.compose.animation.scene.SceneKey import com.android.systemui.CoreStartable import com.android.systemui.communal.domain.interactor.CommunalInteractor @@ -43,6 +42,7 @@ import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine @@ -74,6 +74,10 @@ constructor( ) : CoreStartable { private var screenTimeout: Int = DEFAULT_SCREEN_TIMEOUT + private var timeoutJob: Job? = null + + private var isDreaming: Boolean = false + override fun start() { // Handle automatically switching based on keyguard state. keyguardTransitionInteractor.startedKeyguardTransitionStep @@ -113,47 +117,67 @@ constructor( } .launchIn(bgScope) - // Handle timing out back to the dream. + // The hub mode timeout should start as soon as the user enters hub mode. At the end of the + // timer, if the device is dreaming, hub mode should closed and reveal the dream. If the + // dream is not running, nothing will happen. However if the dream starts again underneath + // hub mode after the initial timeout expires, such as if the device is docked or the dream + // app is updated by the Play store, a new timeout should be started. bgScope.launch { combine( communalInteractor.desiredScene, // Emit a value on start so the combine starts. communalInteractor.userActivity.emitOnStart() ) { scene, _ -> - // Time out should run whenever we're dreaming and the hub is open, even if not - // docked. + // Only timeout if we're on the hub is open. scene == CommunalScenes.Communal } - // mapLatest cancels the previous action block when new values arrive, so any - // already running timeout gets cancelled when conditions change or user interaction - // is detected. - .mapLatest { shouldTimeout -> - if (!shouldTimeout) { - return@mapLatest false + .collectLatest { shouldTimeout -> + cancelHubTimeout() + if (shouldTimeout) { + startHubTimeout() } - - delay(screenTimeout.milliseconds) - true } - .sample(keyguardInteractor.isDreaming, ::Pair) - .collect { (shouldTimeout, isDreaming) -> - if (isDreaming && shouldTimeout) { + } + bgScope.launch { + keyguardInteractor.isDreaming + .sample(communalInteractor.desiredScene, ::Pair) + .collectLatest { (isDreaming, scene) -> + this@CommunalSceneStartable.isDreaming = isDreaming + if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) { + // If dreaming starts after timeout has expired, ex. if dream restarts under + // the hub, just close the hub immediately. communalInteractor.changeScene(CommunalScenes.Blank) } } } - if (dreamTracksFocus()) { - bgScope.launch { - communalInteractor.isIdleOnCommunal.collectLatest { - withContext(mainDispatcher) { - notificationShadeWindowController.setGlanceableHubShowing(it) - } + bgScope.launch { + communalInteractor.isIdleOnCommunal.collectLatest { + withContext(mainDispatcher) { + notificationShadeWindowController.setGlanceableHubShowing(it) } } } } + private fun cancelHubTimeout() { + timeoutJob?.cancel() + timeoutJob = null + } + + private fun startHubTimeout() { + if (timeoutJob == null) { + timeoutJob = + bgScope.launch { + delay(screenTimeout.milliseconds) + if (isDreaming) { + communalInteractor.changeScene(CommunalScenes.Blank) + } + timeoutJob = null + } + } + } + private suspend fun determineSceneAfterTransition( lastStartedTransition: TransitionStep, ): SceneKey? { diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt index 03f54c8b25d7..5cd15f278f00 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt @@ -17,15 +17,23 @@ package com.android.systemui.communal.data.model import android.appwidget.AppWidgetProviderInfo +import com.android.settingslib.flags.Flags.allowAllWidgetsOnLockscreenByDefault /** * The widget categories to display on communal hub (where categories is a bitfield with values that * match those in {@link AppWidgetProviderInfo}). */ @JvmInline -value class CommunalWidgetCategories( - // The default is keyguard widgets. - val categories: Int = AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD -) { +value class CommunalWidgetCategories(val categories: Int = defaultCategories) { fun contains(category: Int) = (categories and category) == category + + companion object { + val defaultCategories: Int + get() { + return AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or + if (allowAllWidgetsOnLockscreenByDefault()) + AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN + else 0 + } + } } 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/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index 9debe0e56083..88cb64c95c04 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -18,7 +18,6 @@ package com.android.systemui.communal.data.repository import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL -import android.appwidget.AppWidgetProviderInfo import android.content.IntentFilter import android.content.pm.UserInfo import android.provider.Settings @@ -108,10 +107,9 @@ constructor( .onStart { emit(Unit) } .map { CommunalWidgetCategories( - // The default is to show only keyguard widgets. secureSettings.getIntForUser( GLANCEABLE_HUB_CONTENT_SETTING, - AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD, + CommunalWidgetCategories.defaultCategories, user.id ) ) 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 7448e14895c6..9599a8864bcc 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 @@ -44,9 +44,10 @@ import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.dock.DockManager -import com.android.systemui.dock.retrieveIsDocked +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog @@ -59,11 +60,11 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.settings.UserTracker import com.android.systemui.smartspace.data.repository.SmartspaceRepository -import com.android.systemui.util.kotlin.BooleanFlowOperators.and +import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not -import com.android.systemui.util.kotlin.BooleanFlowOperators.or import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.BufferOverflow @@ -77,9 +78,11 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.shareIn @@ -92,6 +95,7 @@ class CommunalInteractor @Inject constructor( @Application val applicationScope: CoroutineScope, + @Background val bgDispatcher: CoroutineDispatcher, broadcastDispatcher: BroadcastDispatcher, private val communalRepository: CommunalRepository, private val widgetRepository: CommunalWidgetRepository, @@ -99,13 +103,13 @@ constructor( mediaRepository: CommunalMediaRepository, smartspaceRepository: SmartspaceRepository, keyguardInteractor: KeyguardInteractor, - private val communalSettingsInteractor: CommunalSettingsInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, + communalSettingsInteractor: CommunalSettingsInteractor, private val appWidgetHost: CommunalAppWidgetHost, private val editWidgetsActivityStarter: EditWidgetsActivityStarter, private val userTracker: UserTracker, private val activityStarter: ActivityStarter, private val userManager: UserManager, - private val dockManager: DockManager, sceneInteractor: SceneInteractor, @CommunalLog logBuffer: LogBuffer, @CommunalTableLog tableLogBuffer: TableLogBuffer, @@ -122,10 +126,10 @@ constructor( /** Whether communal features are enabled and available. */ val isCommunalAvailable: Flow<Boolean> = - and( + allOf( communalSettingsInteractor.isCommunalEnabled, not(keyguardInteractor.isEncryptedOrLockdown), - or(keyguardInteractor.isKeyguardShowing, keyguardInteractor.isDreaming) + keyguardInteractor.isKeyguardShowing ) .distinctUntilChanged() .onEach { available -> @@ -145,8 +149,25 @@ constructor( replay = 1, ) - /** Whether to show communal by default */ - val showByDefault: Flow<Boolean> = and(isCommunalAvailable, dockManager.retrieveIsDocked()) + /** Whether to show communal when exiting the occluded state. */ + val showCommunalFromOccluded: Flow<Boolean> = + keyguardTransitionInteractor.startedKeyguardTransitionStep + .filter { step -> step.to == KeyguardState.OCCLUDED } + .combine(isCommunalAvailable, ::Pair) + .map { (step, available) -> available && step.from == KeyguardState.GLANCEABLE_HUB } + .flowOn(bgDispatcher) + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + 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]. @@ -201,7 +222,7 @@ constructor( .flatMapLatest { state -> when (state) { is ObservableTransitionState.Idle -> - flowOf(CommunalTransitionProgress.Idle(state.scene)) + flowOf(CommunalTransitionProgress.Idle(state.currentScene)) is ObservableTransitionState.Transition -> if (state.toScene == targetScene) { state.progress.map { @@ -264,7 +285,9 @@ constructor( */ val isIdleOnCommunal: StateFlow<Boolean> = communalRepository.transitionState - .map { it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Communal } + .map { + it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Communal + } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -278,7 +301,7 @@ constructor( */ val isCommunalVisible: Flow<Boolean> = communalRepository.transitionState.map { - !(it is ObservableTransitionState.Idle && it.scene == CommunalScenes.Blank) + !(it is ObservableTransitionState.Idle && it.currentScene == CommunalScenes.Blank) } /** @@ -386,19 +409,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, + ) + } } } } @@ -413,7 +447,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. */ @@ -496,7 +538,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/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index f9de60984e2d..3e5126a307eb 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -75,7 +75,7 @@ constructor( scope = bgScope, // Start this eagerly since the value can be accessed synchronously. started = SharingStarted.Eagerly, - initialValue = CommunalWidgetCategories().categories + initialValue = CommunalWidgetCategories.defaultCategories ) private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow { 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/log/CommunalLoggerStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt index f2b473864a78..81feb441cfbf 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/log/CommunalLoggerStartable.kt @@ -88,12 +88,12 @@ constructor( /** Whether currently in communal scene. */ private fun ObservableTransitionState.isOnCommunal(): Boolean { - return this is ObservableTransitionState.Idle && scene == CommunalScenes.Communal + return this is ObservableTransitionState.Idle && currentScene == CommunalScenes.Communal } /** Whether currently in a scene other than communal. */ private fun ObservableTransitionState.isNotOnCommunal(): Boolean { - return this is ObservableTransitionState.Idle && scene != CommunalScenes.Communal + return this is ObservableTransitionState.Idle && currentScene != CommunalScenes.Communal } /** Whether currently transitioning from another scene to communal. */ 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..650852ce5876 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 @@ -35,7 +35,6 @@ import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.dagger.MediaModule -import com.android.systemui.res.R import javax.inject.Inject import javax.inject.Named import kotlinx.coroutines.CoroutineDispatcher @@ -96,6 +95,8 @@ constructor( uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL) } + val isIdleOnCommunal: StateFlow<Boolean> = communalInteractor.isIdleOnCommunal + /** Launch the widget picker activity using the given {@link ActivityResultLauncher}. */ suspend fun onOpenWidgetPicker( resources: Resources, @@ -104,7 +105,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) @@ -131,14 +137,6 @@ constructor( return Intent(Intent.ACTION_PICK).apply { setPackage(packageName) putExtra( - EXTRA_DESIRED_WIDGET_WIDTH, - resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_width) - ) - putExtra( - EXTRA_DESIRED_WIDGET_HEIGHT, - resources.getDimensionPixelSize(R.dimen.communal_widget_picker_desired_height) - ) - putExtra( AppWidgetManager.EXTRA_CATEGORY_FILTER, communalSettingsInteractor.communalWidgetCategories.value ) @@ -163,8 +161,6 @@ constructor( companion object { private const val TAG = "CommunalEditModeViewModel" - private const val EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width" - private const val EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height" private const val EXTRA_UI_SURFACE_KEY = "ui_surface" private const val EXTRA_UI_SURFACE_VALUE = "widgets_hub" const val EXTRA_ADDED_APP_WIDGETS_KEY = "added_app_widgets" 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 1bee83b41dbe..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,11 +82,13 @@ constructor( glanceableHubToLockscreenTransitionViewModel.showUmo, dreamToGlanceableHubTransitionViewModel.showUmo, glanceableHubToDreamTransitionViewModel.showUmo, + showUmoFromOccludedToGlanceableHub, + showUmoFromGlanceableHubToOccluded, ) .distinctUntilChanged() - /** Whether to show communal by default */ - val showByDefault: Flow<Boolean> = communalInteractor.showByDefault + /** Whether to show communal when exiting the occluded state. */ + val showCommunalFromOccluded: Flow<Boolean> = communalInteractor.showCommunalFromOccluded val transitionFromOccludedEnded = keyguardTransitionInteractor.transitionStepsFromState(KeyguardState.OCCLUDED).filter { step @@ -74,8 +98,11 @@ constructor( } val recentsBackgroundColor: Flow<Color?> = - combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor -> - if (showByDefault) { + combine(showCommunalFromOccluded, communalColors.backgroundColor) { + showCommunalFromOccluded, + backgroundColor, + -> + if (showCommunalFromOccluded) { backgroundColor } else { null 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..97db43bdf0f8 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,7 @@ 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.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.log.LogBuffer @@ -64,10 +64,10 @@ constructor( @Application private val scope: CoroutineScope, @Main private val resources: Resources, keyguardTransitionInteractor: KeyguardTransitionInteractor, + keyguardInteractor: KeyguardInteractor, 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 +142,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. @@ -240,6 +238,14 @@ constructor( */ val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded) + // TODO(b/339667383): remove this temporary swipe gesture handle + /** + * The dream overlay has its own gesture handle as the SysUI window is not visible above the + * dream. This flow will be false when dreaming so that we don't show a duplicate handle when + * opening the hub over the dream. + */ + val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming) + companion object { const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L } 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 8390d62b23db..301da51c8082 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartable.kt @@ -23,7 +23,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.settings.UserTracker -import com.android.systemui.util.kotlin.BooleanFlowOperators.or +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -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, @@ -46,7 +47,7 @@ constructor( ) : CoreStartable { override fun start() { - or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen) + anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen) // Only trigger updates on state changes, ignoring the initial false value. .pairwise(false) .filter { (previous, new) -> previous != new } @@ -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/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt index 840c3a83b758..25591378938e 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.widgets import android.appwidget.AppWidgetHostView +import android.appwidget.AppWidgetProviderInfo import android.content.Context import android.graphics.Outline import android.graphics.Rect @@ -50,6 +51,11 @@ class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context), enforceRoundedCorners() } + override fun setAppWidget(appWidgetId: Int, info: AppWidgetProviderInfo?) { + super.setAppWidget(appWidgetId, info) + setPadding(0, 0, 0, 0) + } + private val cornerRadiusEnforcementOutline: ViewOutlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View?, outline: Outline) { 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/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt index f20fafccfd19..426f484e4d02 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt @@ -44,6 +44,7 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.Logger import com.android.systemui.log.dagger.CommunalLog import javax.inject.Inject +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch /** An Activity for editing the widgets that appear in hub mode. */ @@ -69,6 +70,8 @@ constructor( private var shouldOpenWidgetPickerOnStart = false + private var lockOnDestroy = false + private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(StartActivityForResult()) { result -> when (result.resultCode) { @@ -149,15 +152,18 @@ constructor( } private fun onEditDone() { - try { + lifecycleScope.launch { communalViewModel.changeScene( CommunalScenes.Communal, CommunalTransitionKeys.SimpleFade ) - checkNotNull(windowManagerService).lockNow(/* options */ null) + + // Wait for the current scene to be idle on communal. + communalViewModel.isIdleOnCommunal.first { it } + // Then finish the activity (this helps to avoid a flash of lockscreen when locking + // in onDestroy()). + lockOnDestroy = true finish() - } catch (e: RemoteException) { - Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") } } @@ -190,5 +196,15 @@ constructor( override fun onDestroy() { super.onDestroy() communalViewModel.setEditModeOpen(false) + + if (lockOnDestroy) lockNow() + } + + private fun lockNow() { + try { + checkNotNull(windowManagerService).lockNow(/* options */ null) + } catch (e: RemoteException) { + Log.e(TAG, "Couldn't lock the device as WindowManager is dead.") + } } } diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt deleted file mode 100644 index 0daa058720ba..000000000000 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.contrast - -import android.app.UiModeManager -import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_HIGH -import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_MEDIUM -import android.app.UiModeManager.ContrastUtils.CONTRAST_LEVEL_STANDARD -import android.app.UiModeManager.ContrastUtils.fromContrastLevel -import android.app.UiModeManager.ContrastUtils.toContrastLevel -import android.os.Bundle -import android.provider.Settings -import android.view.View -import android.widget.FrameLayout -import com.android.internal.annotations.VisibleForTesting -import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.res.R -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.util.settings.SecureSettings -import java.util.concurrent.Executor -import javax.inject.Inject - -/** Dialog to select contrast options */ -class ContrastDialogDelegate -@Inject -constructor( - private val sysuiDialogFactory: SystemUIDialog.Factory, - @Main private val mainExecutor: Executor, - private val uiModeManager: UiModeManager, - private val userTracker: UserTracker, - private val secureSettings: SecureSettings, -) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener { - - @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout> - lateinit var dialogView: View - @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD) - - override fun createDialog(): SystemUIDialog { - val dialog = sysuiDialogFactory.create(this) - dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null) - with(dialog) { - setView(dialogView) - - setTitle(R.string.quick_settings_contrast_label) - setNeutralButton(R.string.cancel) { _, _ -> - secureSettings.putFloatForUser( - Settings.Secure.CONTRAST_LEVEL, - initialContrast, - userTracker.userId - ) - dialog.dismiss() - } - setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() } - } - - return dialog - } - - override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { - contrastButtons = - mapOf( - CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard), - CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium), - CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high) - ) - - contrastButtons.forEach { (contrastLevel, contrastButton) -> - contrastButton.setOnClickListener { - val contrastValue = fromContrastLevel(contrastLevel) - secureSettings.putFloatForUser( - Settings.Secure.CONTRAST_LEVEL, - contrastValue, - userTracker.userId - ) - } - } - - initialContrast = uiModeManager.contrast - highlightContrast(toContrastLevel(initialContrast)) - } - - override fun onStart(dialog: SystemUIDialog) { - uiModeManager.addContrastChangeListener(mainExecutor, this) - } - - override fun onStop(dialog: SystemUIDialog) { - uiModeManager.removeContrastChangeListener(this) - } - - override fun onContrastChanged(contrast: Float) { - highlightContrast(toContrastLevel(contrast)) - } - - private fun highlightContrast(contrast: Int) { - contrastButtons.forEach { (level, button) -> button.isSelected = level == contrast } - } -} 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/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index d2df276002cc..c2e1e33f5318 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -20,7 +20,6 @@ import android.app.Activity; import com.android.systemui.ForegroundServicesDialog; import com.android.systemui.communal.widgets.EditWidgetsActivity; -import com.android.systemui.contrast.ContrastDialogActivity; import com.android.systemui.keyguard.WorkLockActivity; import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.people.widget.LaunchConversationActivity; @@ -72,12 +71,6 @@ public abstract class DefaultActivityBinder { @ClassKey(BrightnessDialog.class) public abstract Activity bindBrightnessDialog(BrightnessDialog activity); - /** Inject into ContrastDialogActivity. */ - @Binds - @IntoMap - @ClassKey(ContrastDialogActivity.class) - public abstract Activity bindContrastDialogActivity(ContrastDialogActivity activity); - /** Inject into UsbDebuggingActivity. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index 98ca09df4c05..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; @@ -76,6 +77,7 @@ import android.net.ConnectivityManager; import android.net.NetworkScoreManager; import android.net.wifi.WifiManager; import android.os.BatteryStats; +import android.os.IDeviceIdleController; import android.os.PowerExemptionManager; import android.os.PowerManager; import android.os.ServiceManager; @@ -223,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(); } @@ -482,6 +491,12 @@ public class FrameworkServicesModule { @Provides @Singleton + static PackageInstaller providePackageInstaller(PackageManager packageManager) { + return packageManager.getPackageInstaller(); + } + + @Provides + @Singleton static PackageManagerWrapper providePackageManagerWrapper() { return PackageManagerWrapper.getInstance(); } @@ -735,4 +750,11 @@ public class FrameworkServicesModule { static Optional<SatelliteManager> provideSatelliteManager(Context context) { return Optional.ofNullable(context.getSystemService(SatelliteManager.class)); } + + @Provides + @Singleton + static IDeviceIdleController provideDeviceIdleController() { + return IDeviceIdleController.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt index ba45a51ad9a3..813fccffb62f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryFaceAuthRepository.kt @@ -46,9 +46,9 @@ import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthR import com.android.systemui.keyguard.data.repository.FaceAuthTableLog import com.android.systemui.keyguard.data.repository.FaceDetectTableLog import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.data.repository.TrustRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions @@ -64,6 +64,7 @@ import com.android.systemui.user.data.repository.UserRepository import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.Arrays +import java.util.concurrent.Executor import java.util.stream.Collectors import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -150,12 +151,12 @@ constructor( @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, @Background private val backgroundDispatcher: CoroutineDispatcher, + @Background private val backgroundExecutor: Executor, private val sessionTracker: SessionTracker, private val uiEventsLogger: UiEventLogger, private val faceAuthLogger: FaceAuthenticationLogger, private val biometricSettingsRepository: BiometricSettingsRepository, private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, - trustRepository: TrustRepository, private val keyguardRepository: KeyguardRepository, private val powerInteractor: PowerInteractor, private val keyguardInteractor: KeyguardInteractor, @@ -235,7 +236,10 @@ constructor( } init { - faceManager?.addLockoutResetCallback(faceLockoutResetCallback) + backgroundExecutor.execute { + faceManager?.addLockoutResetCallback(faceLockoutResetCallback) + faceAuthLogger.addLockoutResetCallbackDone() + } faceAcquiredInfoIgnoreList = Arrays.stream( context.resources.getIntArray( @@ -299,7 +303,7 @@ constructor( private fun listenForSchedulingWatchdog() { keyguardTransitionInteractor - .transition(to = KeyguardState.GONE) + .transition(Edge.create(to = KeyguardState.GONE)) .filter { it.transitionState == TransitionState.FINISHED } .onEach { // We deliberately want to run this in background because scheduleWatchdog does diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt index a32b2aae817a..6ca8eb9449d0 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt @@ -36,6 +36,9 @@ interface DeviceEntryFaceAuthInteractor { val authenticated: Flow<Boolean> + /** Whether bypass is enabled. If enabled, face unlock dismisses the lock screen. */ + val isBypassEnabled: Flow<Boolean> + /** Can face auth be run right now */ fun canFaceAuthRun(): Boolean diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 662974dd2c91..d079a954cb57 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -240,6 +240,15 @@ constructor( } /** + * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has + * chosen any secure authentication method and even if they set the lockscreen to be dismissed + * when the user swipes on it. + */ + suspend fun isLockscreenEnabled(): Boolean { + return repository.isLockscreenEnabled() + } + + /** * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically * dismissed once the authentication challenge is completed. For example, completing a biometric * authentication challenge via face unlock or fingerprint sensor can automatically bypass the diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt index d4f76a84c016..0b9336fec946 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.util.kotlin.sample import javax.inject.Inject @@ -46,10 +46,9 @@ constructor( ) { val deviceEntryFromBiometricSource: Flow<BiometricUnlockSource> = keyguardInteractor.biometricUnlockState - .filter { BiometricUnlockModel.dismissesKeyguard(it) } - .sample( - keyguardInteractor.biometricUnlockSource.filterNotNull(), - ) + .filter { BiometricUnlockMode.dismissesKeyguard(it.mode) } + .map { it.source } + .filterNotNull() private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow() val deviceEntryFromDeviceEntryIcon: Flow<Unit> = diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt index 80b52ed0e055..6c6d730819f3 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor +import com.android.systemui.biometrics.shared.model.SensorLocation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository @@ -58,4 +59,17 @@ constructor( flowOf(false) } } + + /** + * Location of the under-display fingerprint sensor on the display. Null if the device does not + * support UDFPS. + */ + val udfpsLocation: Flow<SensorLocation?> = + isUdfpsSupported.flatMapLatest { + if (it) { + fingerprintPropertyInteractor.sensorLocation + } else { + flowOf(null) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt index 6629f6e2af31..9486798c4dc7 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt @@ -22,6 +22,7 @@ import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flowOf /** * Implementation of the interactor that noops all face auth operations. @@ -35,6 +36,7 @@ class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceA override val detectionStatus: Flow<FaceDetectionStatus> = emptyFlow() override val lockedOut: Flow<Boolean> = emptyFlow() override val authenticated: Flow<Boolean> = emptyFlow() + override val isBypassEnabled: Flow<Boolean> = flowOf(false) override fun canFaceAuthRun(): Boolean = false diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt index 6c6683a483c7..87f3f3cf3a24 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt @@ -38,6 +38,7 @@ import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.DevicePosture +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN @@ -126,9 +127,9 @@ constructor( .launchIn(applicationScope) merge( - keyguardTransitionInteractor.transition(AOD, LOCKSCREEN), - keyguardTransitionInteractor.transition(OFF, LOCKSCREEN), - keyguardTransitionInteractor.transition(DOZING, LOCKSCREEN), + keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)), + keyguardTransitionInteractor.transition(Edge.create(OFF, LOCKSCREEN)), + keyguardTransitionInteractor.transition(Edge.create(DOZING, LOCKSCREEN)), ) .filter { it.transitionState == TransitionState.STARTED } .sample(powerInteractor.detailedWakefulness) @@ -285,6 +286,7 @@ constructor( override val detectionStatus = repository.detectionStatus override val lockedOut: Flow<Boolean> = repository.isLockedOut override val authenticated: Flow<Boolean> = repository.isAuthenticated + override val isBypassEnabled: Flow<Boolean> = repository.isBypassEnabled private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) { if (repository.isLockedOut.value) { 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..1e725eb71dde 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java @@ -16,15 +16,21 @@ package com.android.systemui.dreams; +import static android.service.dreams.Flags.dreamHandlesBeingObscured; + import static com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress; import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamAlphaScaledExpansion; import static com.android.keyguard.BouncerPanelExpansionCalculator.getDreamYPositionScaledExpansion; +import static com.android.systemui.Flags.communalHub; +import static com.android.systemui.Flags.glanceableHubGestureHandle; import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_BOTTOM; import static com.android.systemui.complication.ComplicationLayoutParams.POSITION_TOP; import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; +import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows; import android.animation.Animator; +import android.app.DreamManager; import android.content.res.Resources; import android.graphics.Region; import android.os.Handler; @@ -37,7 +43,9 @@ import com.android.dream.lowlight.LowLightTransitionCoordinator; import com.android.systemui.ambient.touch.scrim.BouncerlessScrimController; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; +import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.complication.ComplicationHostViewController; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.dagger.DreamOverlayComponent; import com.android.systemui.dreams.dagger.DreamOverlayModule; @@ -45,10 +53,12 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.res.R; import com.android.systemui.shade.ShadeExpansionChangeEvent; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.BlurUtils; import com.android.systemui.util.ViewController; import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.flow.FlowKt; import java.util.Arrays; @@ -68,6 +78,8 @@ public class DreamOverlayContainerViewController extends private final DreamOverlayStateController mStateController; private final LowLightTransitionCoordinator mLowLightTransitionCoordinator; private final KeyguardTransitionInteractor mKeyguardTransitionInteractor; + private final ShadeInteractor mShadeInteractor; + private final CommunalInteractor mCommunalInteractor; private final ComplicationHostViewController mComplicationHostViewController; @@ -87,9 +99,10 @@ public class DreamOverlayContainerViewController extends // Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates). private final Handler mHandler; - private final CoroutineDispatcher mMainDispatcher; + private final CoroutineDispatcher mBackgroundDispatcher; private final int mDreamOverlayMaxTranslationY; private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; + private final DreamManager mDreamManager; private long mJitterStartTimeMillis; @@ -174,11 +187,12 @@ public class DreamOverlayContainerViewController extends DreamOverlayContainerView containerView, ComplicationHostViewController complicationHostViewController, @Named(DreamOverlayModule.DREAM_OVERLAY_CONTENT_VIEW) ViewGroup contentView, + @Named(DreamOverlayModule.HUB_GESTURE_INDICATOR_VIEW) View hubGestureIndicatorView, DreamOverlayStatusBarViewController statusBarViewController, LowLightTransitionCoordinator lowLightTransitionCoordinator, BlurUtils blurUtils, @Main Handler handler, - @Main CoroutineDispatcher mainDispatcher, + @Background CoroutineDispatcher backgroundDispatcher, @Main Resources resources, @Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset, @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long @@ -188,23 +202,33 @@ public class DreamOverlayContainerViewController extends DreamOverlayAnimationsController animationsController, DreamOverlayStateController stateController, BouncerlessScrimController bouncerlessScrimController, - KeyguardTransitionInteractor keyguardTransitionInteractor) { + KeyguardTransitionInteractor keyguardTransitionInteractor, + ShadeInteractor shadeInteractor, + CommunalInteractor communalInteractor, + DreamManager dreamManager) { super(containerView); mDreamOverlayContentView = contentView; mStatusBarViewController = statusBarViewController; mBlurUtils = blurUtils; mDreamOverlayAnimationsController = animationsController; mStateController = stateController; + mCommunalInteractor = communalInteractor; mLowLightTransitionCoordinator = lowLightTransitionCoordinator; mBouncerlessScrimController = bouncerlessScrimController; - mBouncerlessScrimController.addCallback(mBouncerlessExpansionCallback); mKeyguardTransitionInteractor = keyguardTransitionInteractor; + mShadeInteractor = shadeInteractor; mComplicationHostViewController = complicationHostViewController; mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize( R.dimen.dream_overlay_y_offset); + + if (communalHub() && glanceableHubGestureHandle()) { + // TODO(b/339667383): remove this temporary swipe gesture handle + hubGestureIndicatorView.setVisibility(View.VISIBLE); + } + final View view = mComplicationHostViewController.getView(); mDreamOverlayContentView.addView(view, @@ -212,11 +236,12 @@ public class DreamOverlayContainerViewController extends ViewGroup.LayoutParams.MATCH_PARENT)); mHandler = handler; - mMainDispatcher = mainDispatcher; + mBackgroundDispatcher = backgroundDispatcher; mMaxBurnInOffset = maxBurnInOffset; mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval; mMillisUntilFullJitter = millisUntilFullJitter; mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor; + mDreamManager = dreamManager; } @Override @@ -234,15 +259,26 @@ 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(); - collectFlow( - mView, - mKeyguardTransitionInteractor.isFinishedInStateWhere(KeyguardState::isBouncerState), - isFinished -> mAnyBouncerShowing = isFinished, - mMainDispatcher); + if (dreamHandlesBeingObscured()) { + collectFlow( + mView, + FlowKt.distinctUntilChanged(combineFlows( + mKeyguardTransitionInteractor.isFinishedInStateWhere( + KeyguardState::isBouncerState), + mShadeInteractor.isAnyExpanded(), + mCommunalInteractor.isCommunalShowing(), + (anyBouncerShowing, shadeExpanded, communalShowing) -> { + mAnyBouncerShowing = anyBouncerShowing; + return anyBouncerShowing || shadeExpanded || communalShowing; + })), + mDreamManager::setDreamIsObscured, + mBackgroundDispatcher); + } // Start dream entry animations. Skip animations for low light clock. if (!mStateController.isLowLightActive()) { @@ -255,8 +291,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/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 999e6813ea55..789b7f8550d7 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -18,6 +18,7 @@ package com.android.systemui.dreams.dagger; import android.content.res.Resources; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; import androidx.lifecycle.Lifecycle; @@ -39,6 +40,7 @@ import javax.inject.Named; @Module public abstract class DreamOverlayModule { public static final String DREAM_OVERLAY_CONTENT_VIEW = "dream_overlay_content_view"; + public static final String HUB_GESTURE_INDICATOR_VIEW = "hub_gesture_indicator_view"; public static final String MAX_BURN_IN_OFFSET = "max_burn_in_offset"; public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL = "burn_in_protection_update_interval"; @@ -71,6 +73,18 @@ public abstract class DreamOverlayModule { "R.id.dream_overlay_content must not be null"); } + /** + * Gesture indicator bar on the right edge of the screen to indicate to users that they can + * swipe to see their widgets on lock screen. + */ + @Provides + @DreamOverlayComponent.DreamOverlayScope + @Named(HUB_GESTURE_INDICATOR_VIEW) + public static View providesHubGestureIndicatorView(DreamOverlayContainerView view) { + return Preconditions.checkNotNull(view.findViewById(R.id.glanceable_hub_handle), + "R.id.glanceable_hub_handle must not be null"); + } + /** */ @Provides public static TouchInsetManager.TouchInsetSession providesTouchInsetSession( 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/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java index fff0c58eecb8..1c047ddcd3d8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java @@ -98,7 +98,7 @@ public class CommunalTouchHandler implements TouchHandler { // Notification shade window has its own logic to be visible if the hub is open, no need to // do anything here other than send touch events over. session.registerInputListener(ev -> { - surfaces.handleExternalShadeWindowTouch((MotionEvent) ev); + surfaces.handleDreamTouch((MotionEvent) ev); if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) { var unused = session.pop(); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt index 221f790b1ab2..c5b3c5335fc8 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel @@ -97,7 +98,7 @@ constructor( .distinctUntilChanged() val transitionEnded = - keyguardTransitionInteractor.transition(from = DREAMING).filter { step -> + keyguardTransitionInteractor.transition(Edge.create(from = DREAMING)).filter { step -> step.transitionState == TransitionState.FINISHED || step.transitionState == TransitionState.CANCELED } diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt index 9876fe4482c0..f04cbb87214f 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt @@ -477,7 +477,7 @@ constructor( } private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) { - Trace.beginSection(entry.name) + Trace.beginSection(entry.name.take(Trace.MAX_SECTION_NAME_LEN)) preamble(entry) val dumpTime = measureTimeMillis(block) footer(entry, dumpTime) diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 67c556409615..140434040ca7 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -29,11 +29,13 @@ import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection import javax.inject.Inject /** A class in which engineers can define flag dependencies */ @@ -49,6 +51,7 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token + PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token // SceneContainer dependencies SceneContainerFlag.getFlagDependencies().forEach { (alpha, beta) -> alpha dependsOn beta } 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/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt index c464ed1d29bb..4875f481cce6 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt @@ -30,6 +30,7 @@ import com.android.app.tracing.coroutines.launch import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.qs.tileimpl.QSTileViewImpl import kotlinx.coroutines.DisposableHandle +import kotlinx.coroutines.flow.filterNotNull object QSLongPressEffectViewBinder { @@ -49,64 +50,56 @@ object QSLongPressEffectViewBinder { launch({ "${tileSpec ?: "unknownTileSpec"}#LongPressEffect#action" }) { var effectAnimator: ValueAnimator? = null - qsLongPressEffect.actionType.collect { action -> - action?.let { - when (it) { - QSLongPressEffect.ActionType.CLICK -> { - tile.performClick() - qsLongPressEffect.clearActionType() - } - QSLongPressEffect.ActionType.LONG_PRESS -> { - tile.prepareForLaunch() - tile.performLongClick() - qsLongPressEffect.clearActionType() - } - QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> { - tile.resetLongPressEffectProperties() - tile.performLongClick() - qsLongPressEffect.clearActionType() - } - QSLongPressEffect.ActionType.START_ANIMATOR -> { - if (effectAnimator?.isRunning != true) { - effectAnimator = - ValueAnimator.ofFloat(0f, 1f).apply { - this.duration = - qsLongPressEffect.effectDuration.toLong() - interpolator = AccelerateDecelerateInterpolator() + qsLongPressEffect.actionType.filterNotNull().collect { action -> + when (action) { + QSLongPressEffect.ActionType.CLICK -> { + tile.performClick() + qsLongPressEffect.clearActionType() + } + QSLongPressEffect.ActionType.LONG_PRESS -> { + tile.prepareForLaunch() + tile.performLongClick() + qsLongPressEffect.clearActionType() + } + QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> { + tile.resetLongPressEffectProperties() + tile.performLongClick() + qsLongPressEffect.clearActionType() + } + QSLongPressEffect.ActionType.START_ANIMATOR -> { + if (effectAnimator?.isRunning != true) { + effectAnimator = + ValueAnimator.ofFloat(0f, 1f).apply { + this.duration = + qsLongPressEffect.effectDuration.toLong() + interpolator = AccelerateDecelerateInterpolator() - doOnStart { - qsLongPressEffect.handleAnimationStart() + doOnStart { qsLongPressEffect.handleAnimationStart() } + addUpdateListener { + val value = animatedValue as Float + if (value == 0f) { + tile.bringToFront() + } else { + tile.updateLongPressEffectProperties(value) } - addUpdateListener { - val value = animatedValue as Float - if (value == 0f) { - tile.bringToFront() - } else { - tile.updateLongPressEffectProperties(value) - } - } - doOnEnd { - qsLongPressEffect.handleAnimationComplete() - } - doOnCancel { - qsLongPressEffect.handleAnimationCancel() - } - start() } - } + doOnEnd { qsLongPressEffect.handleAnimationComplete() } + doOnCancel { qsLongPressEffect.handleAnimationCancel() } + start() + } } - QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> { - effectAnimator?.let { - val pausedProgress = it.animatedFraction - qsLongPressEffect.playReverseHaptics(pausedProgress) - it.reverse() - } - } - QSLongPressEffect.ActionType.CANCEL_ANIMATOR -> { - tile.resetLongPressEffectProperties() - effectAnimator?.cancel() + } + QSLongPressEffect.ActionType.REVERSE_ANIMATOR -> { + effectAnimator?.let { + val pausedProgress = it.animatedFraction + qsLongPressEffect.playReverseHaptics(pausedProgress) + it.reverse() } } + QSLongPressEffect.ActionType.CANCEL_ANIMATOR -> { + tile.resetLongPressEffectProperties() + effectAnimator?.cancel() + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt index c6fb4f9d6956..fc9406bd27d8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt @@ -19,12 +19,13 @@ package com.android.systemui.keyboard import com.android.systemui.keyboard.data.repository.KeyboardRepository import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl +import com.android.systemui.keyboard.shortcut.ShortcutHelperModule import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl import dagger.Binds import dagger.Module -@Module +@Module(includes = [ShortcutHelperModule::class]) abstract class KeyboardModule { @Binds diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt new file mode 100644 index 000000000000..5635f8056b9c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperModule.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut + +import android.app.Activity +import com.android.systemui.CoreStartable +import com.android.systemui.Flags.keyboardShortcutHelperRewrite +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperRepository +import com.android.systemui.keyboard.shortcut.ui.ShortcutHelperActivityStarter +import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity +import dagger.Binds +import dagger.Lazy +import dagger.Module +import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +interface ShortcutHelperModule { + + @Binds + @IntoMap + @ClassKey(ShortcutHelperActivity::class) + fun activity(impl: ShortcutHelperActivity): Activity + + companion object { + @Provides + @IntoMap + @ClassKey(ShortcutHelperActivityStarter::class) + fun starter(implLazy: Lazy<ShortcutHelperActivityStarter>): CoreStartable { + return if (keyboardShortcutHelperRewrite()) { + implLazy.get() + } else { + // No-op implementation when the flag is disabled. + NoOpStartable + } + } + + @Provides + @IntoMap + @ClassKey(ShortcutHelperRepository::class) + fun repo(implLazy: Lazy<ShortcutHelperRepository>): CoreStartable { + return if (keyboardShortcutHelperRewrite()) { + implLazy.get() + } else { + // No-op implementation when the flag is disabled. + NoOpStartable + } + } + } +} + +private object NoOpStartable : CoreStartable { + override fun start() {} +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepository.kt new file mode 100644 index 000000000000..9450af4c804e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperRepository.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import com.android.systemui.CoreStartable +import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Inactive +import com.android.systemui.statusbar.CommandQueue +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow + +@SysUISingleton +class ShortcutHelperRepository +@Inject +constructor( + private val commandQueue: CommandQueue, + private val broadcastDispatcher: BroadcastDispatcher, +) : CoreStartable { + + val state = MutableStateFlow<ShortcutHelperState>(Inactive) + + override fun start() { + registerBroadcastReceiver( + action = Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS, + onReceive = { state.value = Active() } + ) + registerBroadcastReceiver( + action = Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS, + onReceive = { state.value = Inactive } + ) + commandQueue.addCallback( + object : CommandQueue.Callbacks { + override fun dismissKeyboardShortcutsMenu() { + state.value = Inactive + } + + override fun toggleKeyboardShortcutsMenu(deviceId: Int) { + state.value = + if (state.value is Inactive) { + Active(deviceId) + } else { + Inactive + } + } + } + ) + } + + fun hide() { + state.value = Inactive + } + + private fun registerBroadcastReceiver(action: String, onReceive: () -> Unit) { + broadcastDispatcher.registerReceiver( + receiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + onReceive() + } + }, + filter = IntentFilter(action), + flags = Context.RECEIVER_EXPORTED or Context.RECEIVER_VISIBLE_TO_INSTANT_APPS + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt new file mode 100644 index 000000000000..44f1c1e8305f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperInteractor.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperRepository +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState +import com.android.systemui.model.SysUiState +import com.android.systemui.settings.DisplayTracker +import com.android.systemui.shared.system.QuickStepContract +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +@SysUISingleton +class ShortcutHelperInteractor +@Inject +constructor( + private val displayTracker: DisplayTracker, + @Background private val backgroundScope: CoroutineScope, + private val sysUiState: SysUiState, + private val repository: ShortcutHelperRepository +) { + + val state: Flow<ShortcutHelperState> = repository.state + + fun onViewClosed() { + repository.hide() + setSysUiStateFlagEnabled(false) + } + + fun onViewOpened() { + setSysUiStateFlagEnabled(true) + } + + private fun setSysUiStateFlagEnabled(enabled: Boolean) { + backgroundScope.launch { + sysUiState + .setFlag(QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING, enabled) + .commitUpdate(displayTracker.defaultDisplayId) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperState.kt new file mode 100644 index 000000000000..d22d6c88ccc8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutHelperState.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.keyboard.shortcut.shared.model + +sealed interface ShortcutHelperState { + data object Inactive : ShortcutHelperState + + data class Active(val deviceId: Int? = null) : ShortcutHelperState +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarter.kt new file mode 100644 index 000000000000..fbf52e773599 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarter.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.ui + +import android.content.Context +import android.content.Intent +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity +import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@SysUISingleton +class ShortcutHelperActivityStarter( + private val context: Context, + @Application private val applicationScope: CoroutineScope, + private val viewModel: ShortcutHelperViewModel, + private val startActivity: (Intent) -> Unit, +) : CoreStartable { + + @Inject + constructor( + context: Context, + @Application applicationScope: CoroutineScope, + viewModel: ShortcutHelperViewModel, + ) : this( + context, + applicationScope, + viewModel, + startActivity = { intent -> context.startActivity(intent) } + ) + + override fun start() { + applicationScope.launch { + viewModel.shouldShow.collect { shouldShow -> + if (shouldShow) { + startShortcutHelperActivity() + } + } + } + } + + private fun startShortcutHelperActivity() { + startActivity( + Intent(context, ShortcutHelperActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt index 692fbb06e88c..ef4156da4f7b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.keyboard.shortcut +package com.android.systemui.keyboard.shortcut.ui.view import android.graphics.Insets import android.os.Bundle @@ -24,16 +24,25 @@ import androidx.activity.BackEventCompat import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback import androidx.core.view.updatePadding +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope +import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel import com.android.systemui.res.R import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN +import javax.inject.Inject +import kotlinx.coroutines.launch /** * Activity that hosts the new version of the keyboard shortcut helper. It will be used both for * small and large screen devices. */ -class ShortcutHelperActivity : ComponentActivity() { +class ShortcutHelperActivity +@Inject +constructor( + private val viewModel: ShortcutHelperViewModel, +) : ComponentActivity() { private val bottomSheetContainer get() = requireViewById<View>(R.id.shortcut_helper_sheet_container) @@ -53,6 +62,25 @@ class ShortcutHelperActivity : ComponentActivity() { setUpPredictiveBack() setUpSheetDismissListener() setUpDismissOnTouchOutside() + observeFinishRequired() + viewModel.onViewOpened() + } + + override fun onDestroy() { + super.onDestroy() + if (isFinishing) { + viewModel.onViewClosed() + } + } + + private fun observeFinishRequired() { + lifecycleScope.launch { + viewModel.shouldShow.flowWithLifecycle(lifecycle).collect { shouldShow -> + if (!shouldShow) { + finish() + } + } + } } private fun setupEdgeToEdge() { diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt new file mode 100644 index 000000000000..c623f5c23fd9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.ui.viewmodel + +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperInteractor +import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +class ShortcutHelperViewModel +@Inject +constructor( + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val interactor: ShortcutHelperInteractor +) { + + val shouldShow = + interactor.state + .map { it is ShortcutHelperState.Active } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + + fun onViewClosed() { + interactor.onViewClosed() + } + + fun onViewOpened() { + interactor.onViewOpened() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java index ee3706a3ba62..a0b25b930d15 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndication.java @@ -32,6 +32,8 @@ import android.view.View; public class KeyguardIndication { @Nullable private final CharSequence mMessage; + @Nullable + private final boolean mForceAccessibilityLiveRegionAssertive; @NonNull private final ColorStateList mTextColor; @Nullable @@ -49,13 +51,15 @@ public class KeyguardIndication { Drawable icon, View.OnClickListener onClickListener, Drawable background, - Long minVisibilityMillis) { + Long minVisibilityMillis, + Boolean foceAssertive) { mMessage = message; mTextColor = textColor; mIcon = icon; mOnClickListener = onClickListener; mBackground = background; mMinVisibilityMillis = minVisibilityMillis; + mForceAccessibilityLiveRegionAssertive = foceAssertive; } /** @@ -101,6 +105,15 @@ public class KeyguardIndication { return mMinVisibilityMillis; } + + /** + * Whether to force the accessibility live region to be assertive. + */ + public boolean getForceAssertiveAccessibilityLiveRegion() { + return mForceAccessibilityLiveRegionAssertive; + } + + @Override public String toString() { String str = "KeyguardIndication{"; @@ -109,6 +122,7 @@ public class KeyguardIndication { if (mOnClickListener != null) str += " mOnClickListener=" + mOnClickListener; if (mBackground != null) str += " mBackground=" + mBackground; if (mMinVisibilityMillis != null) str += " mMinVisibilityMillis=" + mMinVisibilityMillis; + if (mForceAccessibilityLiveRegionAssertive) str += "mForceAccessibilityLiveRegionAssertive"; str += "}"; return str; } @@ -123,6 +137,7 @@ public class KeyguardIndication { private ColorStateList mTextColor; private Drawable mBackground; private Long mMinVisibilityMillis; + private boolean mForceAccessibilityLiveRegionAssertive; public Builder() { } @@ -178,6 +193,14 @@ public class KeyguardIndication { } /** + * Optional. Can force the accessibility live region to be assertive for this message. + */ + public Builder setForceAccessibilityLiveRegionAssertive() { + this.mForceAccessibilityLiveRegionAssertive = true; + return this; + } + + /** * Build the KeyguardIndication. */ public KeyguardIndication build() { @@ -190,7 +213,7 @@ public class KeyguardIndication { return new KeyguardIndication( mMessage, mTextColor, mIcon, mOnClickListener, mBackground, - mMinVisibilityMillis); + mMinVisibilityMillis, mForceAccessibilityLiveRegionAssertive); } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java index 00ec1a14bb93..44e795c3d22a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java @@ -187,18 +187,15 @@ public class KeyguardIndicationRotateTextViewController extends return; } - // current indication is updated to empty + // Current indication is updated to empty. + // Update to empty even if `currMsgShownForMinTime` is false. if (mCurrIndicationType == type && !hasNewIndication && showAsap) { - if (currMsgShownForMinTime) { - if (mShowNextIndicationRunnable != null) { - mShowNextIndicationRunnable.runImmediately(); - } else { - showIndication(INDICATION_TYPE_NONE); - } + if (mShowNextIndicationRunnable != null) { + mShowNextIndicationRunnable.runImmediately(); } else { - scheduleShowNextIndication(minShowDuration - timeSinceLastIndicationSwitch); + showIndication(INDICATION_TYPE_NONE); } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 674c128a580e..f9adc473b119 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -84,20 +84,25 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.ScreenPowerState; +import com.android.systemui.scene.domain.interactor.SceneInteractor; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.DisplayTracker; import com.android.wm.shell.shared.CounterRotator; import com.android.wm.shell.shared.ShellTransitions; import com.android.wm.shell.shared.TransitionUtil; import com.android.wm.shell.transition.Transitions; +import dagger.Lazy; + +import kotlinx.coroutines.CoroutineScope; + import java.util.ArrayList; import java.util.Map; import java.util.WeakHashMap; import javax.inject.Inject; -import kotlinx.coroutines.CoroutineScope; - public class KeyguardService extends Service { static final String TAG = "KeyguardService"; static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD; @@ -109,6 +114,7 @@ public class KeyguardService extends Service { private final ShellTransitions mShellTransitions; private final DisplayTracker mDisplayTracker; private final PowerInteractor mPowerInteractor; + private final Lazy<SceneInteractor> mSceneInteractorLazy; private static RemoteAnimationTarget[] wrap(TransitionInfo info, boolean wallpapers, SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap, @@ -316,7 +322,8 @@ public class KeyguardService extends Service { @Application CoroutineScope scope, FeatureFlags featureFlags, PowerInteractor powerInteractor, - WindowManagerOcclusionManager windowManagerOcclusionManager) { + WindowManagerOcclusionManager windowManagerOcclusionManager, + Lazy<SceneInteractor> sceneInteractorLazy) { super(); mKeyguardViewMediator = keyguardViewMediator; mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher; @@ -325,6 +332,7 @@ public class KeyguardService extends Service { mDisplayTracker = displayTracker; mFlags = featureFlags; mPowerInteractor = powerInteractor; + mSceneInteractorLazy = sceneInteractorLazy; if (KeyguardWmStateRefactor.isEnabled()) { WindowManagerLockscreenVisibilityViewBinder.bind( @@ -601,6 +609,10 @@ public class KeyguardService extends Service { trace("showDismissibleKeyguard"); checkPermission(); mKeyguardViewMediator.showDismissibleKeyguard(); + if (SceneContainerFlag.isEnabled()) { + mSceneInteractorLazy.get().changeScene( + Scenes.Lockscreen, "KeyguardService.showDismissibleKeyguard"); + } } @Override // Binder interface diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java index dbaa297b7b43..68a252b2caba 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java @@ -37,6 +37,7 @@ import android.provider.Settings; import android.service.notification.ZenModeConfig; import android.text.TextUtils; import android.text.style.StyleSpan; +import android.util.Log; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; @@ -212,21 +213,27 @@ public class KeyguardSliceProvider extends SliceProvider implements @AnyThread @Override public Slice onBindSlice(Uri sliceUri) { - Trace.beginSection("KeyguardSliceProvider#onBindSlice"); - Slice slice; - synchronized (this) { - ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY); - if (needsMediaLocked()) { - addMediaLocked(builder); - } else { - builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText)); + Slice slice = null; + try { + Trace.beginSection("KeyguardSliceProvider#onBindSlice"); + synchronized (this) { + ListBuilder builder = new ListBuilder(getContext(), mSliceUri, + ListBuilder.INFINITY); + if (needsMediaLocked()) { + addMediaLocked(builder); + } else { + builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText)); + } + addNextAlarmLocked(builder); + addZenModeLocked(builder); + addPrimaryActionLocked(builder); + slice = builder.build(); } - addNextAlarmLocked(builder); - addZenModeLocked(builder); - addPrimaryActionLocked(builder); - slice = builder.build(); + } catch (IllegalStateException e) { + Log.w(TAG, "Could not initialize slice", e); + } finally { + Trace.endSection(); } - Trace.endSection(); return slice; } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5d31d1e0e7af..81c2d92d29e8 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; @@ -177,10 +176,6 @@ import com.android.systemui.util.time.SystemClock; import com.android.systemui.wallpapers.data.repository.WallpaperRepository; import com.android.wm.shell.keyguard.KeyguardTransitions; -import dagger.Lazy; - -import kotlinx.coroutines.CoroutineDispatcher; - import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -190,6 +185,9 @@ import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; +import dagger.Lazy; +import kotlinx.coroutines.CoroutineDispatcher; + /** * Mediates requests related to the keyguard. This includes queries about the * state of the keyguard, power management events that effect whether the keyguard @@ -1078,6 +1076,33 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } }; + /** + * For now, the keyguard-appearing animation is a no-op, because we assume that this is + * happening while the screen is already off or turning off. + * + * TODO(b/278086361): create an animation for keyguard appearing over a non-showWhenLocked + * activity. + */ + private final IRemoteAnimationRunner.Stub mAppearAnimationRunner = + new IRemoteAnimationRunner.Stub() { + @Override + public void onAnimationStart(@WindowManager.TransitionOldType int transit, + RemoteAnimationTarget[] apps, + RemoteAnimationTarget[] wallpapers, + RemoteAnimationTarget[] nonApps, + IRemoteAnimationFinishedCallback finishedCallback) { + try { + finishedCallback.onAnimationFinished(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to finish transition", e); + } + } + + @Override + public void onAnimationCancelled() { + } + }; + private final IRemoteAnimationRunner mOccludeAnimationRunner = new OccludeActivityLaunchRemoteAnimationRunner(mOccludeAnimationController); @@ -1166,7 +1191,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, finishedCallback.onAnimationFinished(); mOccludeByDreamAnimator = null; } catch (RemoteException e) { - e.printStackTrace(); + Log.e(TAG, "Failed to finish transition", e); } } }); @@ -1234,7 +1259,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mUnoccludeAnimator.cancel(); } - if (isDream || mShowCommunalByDefault) { + if (isDream || mShowCommunalWhenUnoccluding) { initAlphaForAnimationTargets(wallpapers); if (isDream) { mDreamViewModel.get().startTransitionFromDream(); @@ -1281,7 +1306,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); } catch (RemoteException e) { - e.printStackTrace(); + Log.e(TAG, "Failed to finish transition", e); } } }); @@ -1372,7 +1397,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final Lazy<DreamViewModel> mDreamViewModel; private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel; private RemoteAnimationTarget mRemoteAnimationTarget; - private boolean mShowCommunalByDefault = false; + private boolean mShowCommunalWhenUnoccluding = false; private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager; @@ -1547,6 +1572,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mKeyguardTransitions.register( KeyguardService.wrap(this, getExitAnimationRunner()), + KeyguardService.wrap(this, getAppearAnimationRunner()), KeyguardService.wrap(this, getOccludeAnimationRunner()), KeyguardService.wrap(this, getOccludeByDreamAnimationRunner()), KeyguardService.wrap(this, getUnoccludeAnimationRunner())); @@ -1630,8 +1656,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, getRemoteSurfaceAlphaApplier()); mJavaAdapter.alwaysCollectFlow(dreamViewModel.getTransitionEnded(), getFinishedCallbackConsumer()); - mJavaAdapter.alwaysCollectFlow(communalViewModel.getShowByDefault(), - (showByDefault) -> mShowCommunalByDefault = showByDefault); + mJavaAdapter.alwaysCollectFlow(communalViewModel.getShowCommunalFromOccluded(), + (showCommunalFromOccluded) -> { + mShowCommunalWhenUnoccluding = showCommunalFromOccluded; + }); mJavaAdapter.alwaysCollectFlow(communalViewModel.getTransitionFromOccludedEnded(), getFinishedCallbackConsumer()); } @@ -2123,6 +2151,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, return validatingRemoteAnimationRunner(mExitAnimationRunner); } + public IRemoteAnimationRunner getAppearAnimationRunner() { + return validatingRemoteAnimationRunner(mAppearAnimationRunner); + } + public IRemoteAnimationRunner getOccludeAnimationRunner() { if (KeyguardWmStateRefactor.isEnabled()) { return validatingRemoteAnimationRunner(mWmOcclusionManager.getOccludeAnimationRunner()); @@ -3214,6 +3246,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mHideAnimationRun = false; adjustStatusBarLocked(); sendUserPresentBroadcast(); + + if (!KeyguardWmStateRefactor.isEnabled()) { + mKeyguardInteractor.dismissKeyguard(); + } } private Configuration.Builder createInteractionJankMonitorConf(int cuj) { @@ -3352,7 +3388,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } catch (RemoteException e) { mSurfaceBehindRemoteAnimationRequested = false; - e.printStackTrace(); + Log.e(TAG, "Failed to report keyguardGoingAway", e); } } @@ -3435,12 +3471,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); } @@ -3466,11 +3499,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/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt index a65a8827fa48..3cbcb2cb4a0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt @@ -29,15 +29,20 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.utils.GlobalWindowManager import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -59,6 +64,7 @@ constructor( @Application private val applicationScope: CoroutineScope, @Background private val bgDispatcher: CoroutineDispatcher, private val featureFlags: FeatureFlags, + private val sceneInteractor: SceneInteractor, ) : CoreStartable, WakefulnessLifecycle.Observer { override fun start() { @@ -84,9 +90,15 @@ constructor( applicationScope.launch(bgDispatcher) { // We drop 1 to avoid triggering on initial collect(). - keyguardTransitionInteractor.transition(to = GONE).collect { transition -> - if (transition.transitionState == TransitionState.FINISHED) { - onKeyguardGone() + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState + .filter { it.isIdle(Scenes.Gone) } + .collect { onKeyguardGone() } + } else { + keyguardTransitionInteractor.transition(Edge.create(to = GONE)).collect { + if (it.transitionState == TransitionState.FINISHED) { + onKeyguardGone() + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt index 00f50023b263..1b342edb28fe 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt @@ -23,6 +23,7 @@ import android.view.RemoteAnimationTarget import android.view.WindowManager import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier import com.android.systemui.statusbar.policy.KeyguardStateController import java.util.concurrent.Executor @@ -40,6 +41,7 @@ constructor( private val activityTaskManagerService: IActivityTaskManager, private val keyguardStateController: KeyguardStateController, private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /** @@ -141,6 +143,14 @@ constructor( finishedCallback: IRemoteAnimationFinishedCallback ) { if (apps.isNotEmpty()) { + // Ensure that we've started a dismiss keyguard transition. WindowManager can start the + // going away animation on its own, if an activity launches and then requests dismissing + // the keyguard. In this case, this is the first and only signal we'll receive to start + // a transition to GONE. + keyguardTransitionInteractor.startDismissKeyguardTransition( + reason = "Going away remote animation started" + ) + goingAwayRemoteAnimationFinishedCallback = finishedCallback keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0]) } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt index a49b3ae7b7e3..c11c49c7a8a0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository import android.os.Handler import android.util.Log +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.model.KeyguardBlueprint @@ -57,21 +58,7 @@ constructor( TreeMap<String, KeyguardBlueprint>().apply { putAll(blueprints.associateBy { it.id }) } val blueprint: MutableStateFlow<KeyguardBlueprint> = MutableStateFlow(blueprintIdMap[DEFAULT]!!) val refreshTransition = MutableSharedFlow<Config>(extraBufferCapacity = 1) - private var targetTransitionConfig: Config? = null - - /** - * Emits the blueprint value to the collectors. - * - * @param blueprintId - * @return whether the transition has succeeded. - */ - fun applyBlueprint(index: Int): Boolean { - ArrayList(blueprintIdMap.values)[index]?.let { - applyBlueprint(it) - return true - } - return false - } + @VisibleForTesting var targetTransitionConfig: Config? = null /** * Emits the blueprint value to the collectors. @@ -81,27 +68,21 @@ constructor( */ fun applyBlueprint(blueprintId: String?): Boolean { val blueprint = blueprintIdMap[blueprintId] - return if (blueprint != null) { - applyBlueprint(blueprint) - true - } else { + if (blueprint == null) { Log.e( TAG, "Could not find blueprint with id: $blueprintId. " + "Perhaps it was not added to KeyguardBlueprintModule?" ) - false + return false } - } - /** Emits the blueprint value to the collectors. */ - fun applyBlueprint(blueprint: KeyguardBlueprint?) { if (blueprint == this.blueprint.value) { - refreshBlueprint() - return + return true } - blueprint?.let { this.blueprint.value = it } + this.blueprint.value = blueprint + return true } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index 462d8373a430..8a53dd18541c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -17,7 +17,6 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point -import android.hardware.biometrics.BiometricSourceType import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.biometrics.AuthController @@ -31,6 +30,7 @@ import com.android.systemui.doze.DozeMachine import com.android.systemui.doze.DozeTransitionCallback import com.android.systemui.doze.DozeTransitionListener import com.android.systemui.dreams.DreamOverlayCallbackController +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DismissAction @@ -169,10 +169,13 @@ interface KeyguardRepository { /** Observable for the [StatusBarState] */ val statusBarState: StateFlow<StatusBarState> - /** Observable for biometric unlock modes */ + /** Observable for biometric unlock state which includes the mode and unlock source */ val biometricUnlockState: Flow<BiometricUnlockModel> - fun setBiometricUnlockState(value: BiometricUnlockModel) + fun setBiometricUnlockState( + unlockMode: BiometricUnlockMode, + unlockSource: BiometricUnlockSource?, + ) /** Approximate location on the screen of the fingerprint sensor. */ val fingerprintSensorLocation: Flow<Point?> @@ -180,9 +183,6 @@ interface KeyguardRepository { /** Approximate location on the screen of the face unlock sensor/front facing camera. */ val faceSensorLocation: Flow<Point?> - /** Source of the most recent biometric unlock, such as fingerprint or face. */ - val biometricUnlockSource: Flow<BiometricUnlockSource?> - /** Whether quick settings or quick-quick settings is visible. */ val isQuickSettingsVisible: Flow<Boolean> @@ -597,11 +597,15 @@ constructor( statusBarStateIntToObject(statusBarStateController.state) ) - private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) + private val _biometricUnlockState: MutableStateFlow<BiometricUnlockModel> = + MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null)) override val biometricUnlockState = _biometricUnlockState.asStateFlow() - override fun setBiometricUnlockState(value: BiometricUnlockModel) { - _biometricUnlockState.value = value + override fun setBiometricUnlockState( + unlockMode: BiometricUnlockMode, + unlockSource: BiometricUnlockSource?, + ) { + _biometricUnlockState.value = BiometricUnlockModel(unlockMode, unlockSource) } override val fingerprintSensorLocation: Flow<Point?> = conflatedCallbackFlow { @@ -628,27 +632,6 @@ constructor( override val faceSensorLocation: Flow<Point?> = facePropertyRepository.sensorLocation - override val biometricUnlockSource: Flow<BiometricUnlockSource?> = conflatedCallbackFlow { - val callback = - object : KeyguardUpdateMonitorCallback() { - override fun onBiometricAuthenticated( - userId: Int, - biometricSourceType: BiometricSourceType?, - isStrongBiometric: Boolean - ) { - trySendWithFailureLogging( - BiometricUnlockSource.fromBiometricSourceType(biometricSourceType), - TAG, - "onBiometricAuthenticated" - ) - } - } - - keyguardUpdateMonitor.registerCallback(callback) - trySendWithFailureLogging(null, TAG, "initial value") - awaitClose { keyguardUpdateMonitor.removeCallback(callback) } - } - private val _isQuickSettingsVisible = MutableStateFlow(false) override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt index 956125ce372f..a1e4af5d5d20 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt @@ -51,10 +51,6 @@ constructor( ) : KeyguardSmartspaceRepository { private val _bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(View.GONE) override val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow() - val defaultValue = - context.resources.getBoolean( - com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault - ) override val isWeatherEnabled: StateFlow<Boolean> = secureSettings .observerFlow( @@ -76,7 +72,7 @@ constructor( private fun getLockscreenWeatherEnabled(): Boolean { return secureSettings.getIntForUser( Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED, - if (defaultValue) 1 else 0, + 1, userTracker.userId ) == 1 } 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 4c54bfd3a17c..f488d3b67fc7 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 @@ -34,6 +34,7 @@ import java.util.UUID import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -42,6 +43,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.sync.Mutex /** * The source of truth for all keyguard transitions. @@ -89,6 +91,12 @@ interface KeyguardTransitionRepository { suspend fun startTransition(info: TransitionInfo): UUID? /** + * Emits STARTED and FINISHED transition steps to the given state. This is used during boot to + * seed the repository with the appropriate initial state. + */ + suspend fun emitInitialStepsFromOff(to: KeyguardState) + + /** * Allows manual control of a transition. When calling [startTransition], the consumer must pass * in a null animator. In return, it will get a unique [UUID] that will be validated to allow * further updates. @@ -123,12 +131,13 @@ constructor( private var lastStep: TransitionStep = TransitionStep() private var lastAnimator: ValueAnimator? = null + private val _currentTransitionMutex = Mutex() private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> = MutableStateFlow( TransitionInfo( ownerName = "", from = KeyguardState.OFF, - to = KeyguardState.LOCKSCREEN, + to = KeyguardState.OFF, animator = null ) ) @@ -140,17 +149,40 @@ constructor( */ private var updateTransitionId: UUID? = null + // Only used in a test environment + var forceDelayForRaceConditionTest = false + init { - // Seed with transitions signaling a boot into lockscreen state. If updating this, please - // also update FakeKeyguardTransitionRepository. - initialTransitionSteps.forEach(::emitTransition) + // Start with a FINISHED transition in OFF. KeyguardBootInteractor will transition from OFF + // to either GONE or LOCKSCREEN once we're booted up and can determine which state we should + // start in. + emitTransition( + TransitionStep( + KeyguardState.OFF, + KeyguardState.OFF, + 1f, + TransitionState.FINISHED, + ) + ) } override suspend fun startTransition(info: TransitionInfo): UUID? { _currentTransitionInfo.value = info + Log.d(TAG, "(Internal) Setting current transition info: $info") + + // There is no fairness guarantee with 'withContext', which means that transitions could + // be processed out of order. Use a Mutex to guarantee ordering. + _currentTransitionMutex.lock() + + // Only used in a test environment + if (forceDelayForRaceConditionTest) { + delay(50L) + } // Animators must be started on the main thread. return withContext("$TAG#startTransition", mainDispatcher) { + _currentTransitionMutex.unlock() + if (lastStep.from == info.from && lastStep.to == info.to) { Log.i(TAG, "Duplicate call to start the transition, rejecting: $info") return@withContext null @@ -233,7 +265,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 } @@ -251,6 +283,36 @@ constructor( lastStep = nextStep } + override suspend fun emitInitialStepsFromOff(to: KeyguardState) { + _currentTransitionInfo.value = + TransitionInfo( + ownerName = "KeyguardTransitionRepository(boot)", + from = KeyguardState.OFF, + to = to, + animator = null + ) + + emitTransition( + TransitionStep( + KeyguardState.OFF, + to, + 0f, + TransitionState.STARTED, + ownerName = "KeyguardTransitionRepository(boot)", + ) + ) + + emitTransition( + TransitionStep( + KeyguardState.OFF, + to, + 1f, + TransitionState.FINISHED, + ownerName = "KeyguardTransitionRepository(boot)", + ), + ) + } + private fun logAndTrace(step: TransitionStep, isManual: Boolean) { if (step.transitionState == TransitionState.RUNNING) { return @@ -271,31 +333,5 @@ constructor( companion object { private const val TAG = "KeyguardTransitionRepository" - - /** - * Transition steps to seed the repository with, so that all of the transition interactor - * flows emit reasonable initial values. - */ - val initialTransitionSteps: List<TransitionStep> = - listOf( - TransitionStep( - KeyguardState.OFF, - KeyguardState.OFF, - 1f, - TransitionState.FINISHED, - ), - TransitionStep( - KeyguardState.OFF, - KeyguardState.LOCKSCREEN, - 0f, - TransitionState.STARTED, - ), - TransitionStep( - KeyguardState.OFF, - KeyguardState.LOCKSCREEN, - 1f, - TransitionState.FINISHED, - ), - ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt index eac476fcb368..0a15bbf18249 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt @@ -24,7 +24,7 @@ import androidx.core.animation.Animator import androidx.core.animation.ValueAnimator import com.android.keyguard.logging.ScrimLogger import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.power.data.repository.PowerRepository import com.android.systemui.power.shared.model.WakeSleepReason @@ -40,7 +40,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf @@ -83,7 +82,7 @@ constructor( } /** The reveal effect used if the device was locked/unlocked via the power button. */ - private val powerButtonRevealEffect: Flow<LightRevealEffect?> = + private val powerButtonRevealEffect: Flow<LightRevealEffect> = flowOf( PowerButtonReveal( context.resources @@ -92,42 +91,31 @@ constructor( ) ) - private val tapRevealEffect: Flow<LightRevealEffect?> = + private val tapRevealEffect: Flow<LightRevealEffect> = keyguardRepository.lastDozeTapToWakePosition.map { - it?.let { constructCircleRevealFromPoint(it) } + it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT } /** * Reveal effect to use for a fingerprint unlock. This is reconstructed if the fingerprint * sensor location on the screen (in pixels) changes due to configuration changes. */ - private val fingerprintRevealEffect: Flow<LightRevealEffect?> = + private val fingerprintRevealEffect: Flow<LightRevealEffect> = keyguardRepository.fingerprintSensorLocation.map { - it?.let { constructCircleRevealFromPoint(it) } + it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT } /** * Reveal effect to use for a face unlock. This is reconstructed if the face sensor/front camera * location on the screen (in pixels) changes due to configuration changes. */ - private val faceRevealEffect: Flow<LightRevealEffect?> = - keyguardRepository.faceSensorLocation.map { it?.let { constructCircleRevealFromPoint(it) } } - - /** - * The reveal effect we'll use for the next biometric unlock animation. We switch between the - * fingerprint/face unlock effect flows depending on the biometric unlock source. - */ - private val biometricRevealEffect: Flow<LightRevealEffect?> = - keyguardRepository.biometricUnlockSource.flatMapLatest { source -> - when (source) { - BiometricUnlockSource.FINGERPRINT_SENSOR -> fingerprintRevealEffect - BiometricUnlockSource.FACE_SENSOR -> faceRevealEffect - else -> flowOf(null) - } + private val faceRevealEffect: Flow<LightRevealEffect> = + keyguardRepository.faceSensorLocation.map { + it?.let { constructCircleRevealFromPoint(it) } ?: DEFAULT_REVEAL_EFFECT } /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */ - private val nonBiometricRevealEffect: Flow<LightRevealEffect?> = + private val nonBiometricRevealEffect: Flow<LightRevealEffect> = powerRepository.wakefulness.flatMapLatest { wakefulnessModel -> when { wakefulnessModel.isAwakeOrAsleepFrom(WakeSleepReason.POWER_BUTTON) -> @@ -169,29 +157,22 @@ constructor( scrimLogger.d(TAG, "startRevealAmountAnimator, reveal", reveal) } - override val revealEffect = - combine( - keyguardRepository.biometricUnlockState, - biometricRevealEffect, - nonBiometricRevealEffect - ) { biometricUnlockState, biometricReveal, nonBiometricReveal -> - + override val revealEffect: Flow<LightRevealEffect> = + keyguardRepository.biometricUnlockState + .flatMapLatest { biometricUnlockState -> // Use the biometric reveal for any flavor of wake and unlocking. - val revealEffect = - when (biometricUnlockState) { - BiometricUnlockModel.WAKE_AND_UNLOCK, - BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING, - BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM -> biometricReveal - else -> nonBiometricReveal + when (biometricUnlockState.mode) { + BiometricUnlockMode.WAKE_AND_UNLOCK, + BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING, + BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM -> { + if (biometricUnlockState.source == BiometricUnlockSource.FACE_SENSOR) { + faceRevealEffect + } else { + fingerprintRevealEffect + } } - ?: DEFAULT_REVEAL_EFFECT - - scrimLogger.d( - TAG, - "revealEffect", - "$revealEffect, biometricUnlockState: ${biometricUnlockState.name}" - ) - return@combine revealEffect + else -> nonBiometricRevealEffect + } } .distinctUntilChanged() 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/BiometricUnlockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt index cb003a760b26..576fafdc903e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt @@ -2,7 +2,8 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_ONLY_WAKE @@ -13,7 +14,9 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_ import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +@ExperimentalCoroutinesApi @SysUISingleton class BiometricUnlockInteractor @Inject @@ -21,21 +24,24 @@ constructor( private val keyguardRepository: KeyguardRepository, ) { - fun setBiometricUnlockState(@WakeAndUnlockMode unlockStateInt: Int) { + fun setBiometricUnlockState( + @WakeAndUnlockMode unlockStateInt: Int, + biometricUnlockSource: BiometricUnlockSource?, + ) { val state = biometricModeIntToObject(unlockStateInt) - keyguardRepository.setBiometricUnlockState(state) + keyguardRepository.setBiometricUnlockState(state, biometricUnlockSource) } - private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel { + private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockMode { return when (value) { - MODE_NONE -> BiometricUnlockModel.NONE - MODE_WAKE_AND_UNLOCK -> BiometricUnlockModel.WAKE_AND_UNLOCK - MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING - MODE_SHOW_BOUNCER -> BiometricUnlockModel.SHOW_BOUNCER - MODE_ONLY_WAKE -> BiometricUnlockModel.ONLY_WAKE - MODE_UNLOCK_COLLAPSING -> BiometricUnlockModel.UNLOCK_COLLAPSING - MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM - MODE_DISMISS_BOUNCER -> BiometricUnlockModel.DISMISS_BOUNCER + MODE_NONE -> BiometricUnlockMode.NONE + MODE_WAKE_AND_UNLOCK -> BiometricUnlockMode.WAKE_AND_UNLOCK + MODE_WAKE_AND_UNLOCK_PULSING -> BiometricUnlockMode.WAKE_AND_UNLOCK_PULSING + MODE_SHOW_BOUNCER -> BiometricUnlockMode.SHOW_BOUNCER + MODE_ONLY_WAKE -> BiometricUnlockMode.ONLY_WAKE + MODE_UNLOCK_COLLAPSING -> BiometricUnlockMode.UNLOCK_COLLAPSING + MODE_WAKE_AND_UNLOCK_FROM_DREAM -> BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM + MODE_DISMISS_BOUNCER -> BiometricUnlockMode.DISMISS_BOUNCER else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value") } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt index eef4b97ae34d..96260770d89f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context +import android.util.Log import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor @@ -39,6 +40,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch /** @@ -96,10 +98,13 @@ constructor( keyguardUpdateMonitor.isFingerprintDetectionRunning && keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed } + .onEach { Log.d(TAG, "showIndicatorForPrimaryBouncer updated: $it") } private val showIndicatorForAlternateBouncer: Flow<Boolean> = // Note: this interactor internally verifies that SideFPS is enabled and running. - alternateBouncerInteractor.isVisible + alternateBouncerInteractor.isVisible.onEach { + Log.d(TAG, "showIndicatorForAlternateBouncer updated: $it") + } /** * Indicates whether the primary or alternate bouncers request showing the side fingerprint @@ -112,6 +117,7 @@ constructor( showForPrimaryBouncer || showForAlternateBouncer } .distinctUntilChanged() + .onEach { Log.d(TAG, "showIndicatorForDeviceEntry updated: $it") } private fun isBouncerActive(): Boolean { if (SceneContainerFlag.isEnabled) { 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 c2843d839d15..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 @@ -24,10 +24,11 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock 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 @@ -72,7 +73,8 @@ constructor( keyguardInteractor.isKeyguardDismissible, keyguardInteractor.biometricUnlockState, ) { isKeyguardShowing, isKeyguardDismissible, biometricUnlockState -> - (isWakeAndUnlock(biometricUnlockState) || (!isKeyguardShowing && isKeyguardDismissible)) + (isWakeAndUnlock(biometricUnlockState.mode) || + (!isKeyguardShowing && isKeyguardDismissible)) } /** @@ -110,11 +112,11 @@ constructor( // receiving a call to #dismissAod() shortly when the authentication // completes. !maybeStartTransitionToOccludedOrInsecureCamera() && - !isWakeAndUnlock(biometricUnlockState) && + !isWakeAndUnlock(biometricUnlockState.mode) && !primaryBouncerShowing } else { !isKeyguardOccludedLegacy && - !isWakeAndUnlock(biometricUnlockState) && + !isWakeAndUnlock(biometricUnlockState.mode) && !primaryBouncerShowing } @@ -184,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/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt index ca7fc669cb91..115fc3610ac8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt @@ -24,7 +24,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode.Companion.isWakeAndUnlock 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 @@ -66,7 +66,7 @@ constructor( listenForTransitionToCamera(scope, keyguardInteractor) } - private val canDismissLockScreen: Flow<Boolean> = + private val canTransitionToGoneOnWake: Flow<Boolean> = combine( keyguardInteractor.isKeyguardShowing, keyguardInteractor.isKeyguardDismissible, @@ -87,7 +87,7 @@ constructor( keyguardInteractor.biometricUnlockState, keyguardInteractor.isKeyguardOccluded, communalInteractor.isIdleOnCommunal, - canDismissLockScreen, + canTransitionToGoneOnWake, keyguardInteractor.primaryBouncerShowing, ) .collect { @@ -96,12 +96,12 @@ constructor( biometricUnlockState, occluded, isIdleOnCommunal, - canDismissLockScreen, + canTransitionToGoneOnWake, primaryBouncerShowing) -> startTransitionTo( - if (isWakeAndUnlock(biometricUnlockState)) { + if (isWakeAndUnlock(biometricUnlockState.mode)) { KeyguardState.GONE - } else if (canDismissLockScreen) { + } else if (canTransitionToGoneOnWake) { KeyguardState.GONE } else if (primaryBouncerShowing) { KeyguardState.PRIMARY_BOUNCER @@ -129,7 +129,7 @@ constructor( .sample( communalInteractor.isIdleOnCommunal, keyguardInteractor.biometricUnlockState, - canDismissLockScreen, + canTransitionToGoneOnWake, keyguardInteractor.primaryBouncerShowing, ) .collect { @@ -142,7 +142,7 @@ constructor( if ( !maybeStartTransitionToOccludedOrInsecureCamera() && // Handled by dismissFromDozing(). - !isWakeAndUnlock(biometricUnlockState) + !isWakeAndUnlock(biometricUnlockState.mode) ) { startTransitionTo( if (canDismissLockscreen) { 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 10d1e150e6d2..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 @@ -22,10 +22,11 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +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,10 +104,12 @@ 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 -> - biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + biometricUnlockState.mode == BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM } .collect { startTransitionTo(KeyguardState.GONE) } } 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 69c2c78db445..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 @@ -25,10 +25,11 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +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,10 +195,12 @@ 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 -> - biometricUnlockState == BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + biometricUnlockState.mode == BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM } .collect { startTransitionTo(KeyguardState.GONE) } } 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 54d9a78620e5..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.util.kotlin.BooleanFlowOperators.and +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 } @@ -148,7 +153,7 @@ constructor( } } else { scope.launch { - and(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming)) + allOf(keyguardInteractor.isKeyguardOccluded, not(keyguardInteractor.isDreaming)) .filterRelevantKeyguardStateAnd { isOccludedAndNotDreaming -> isOccludedAndNotDreaming } @@ -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..f1e98f3bbe6d 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 @@ -259,7 +263,9 @@ constructor( } fun dismissKeyguard() { - scope.launch("$TAG#dismissKeyguard") { startTransitionTo(KeyguardState.GONE) } + scope.launch("$TAG#dismissKeyguard") { + startTransitionTo(KeyguardState.GONE, ownerReason = "#dismissKeyguard()") + } } private fun listenForLockscreenToGone() { @@ -280,6 +286,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 +344,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 ee589f4a235c..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 @@ -87,17 +89,17 @@ constructor( scope.launch { keyguardOcclusionInteractor.isShowWhenLockedActivityOnTop .filterRelevantKeyguardStateAnd { onTop -> !onTop } - .sample(communalInteractor.isIdleOnCommunal, communalInteractor.showByDefault) - .collect { (_, isIdleOnCommunal, showCommunalByDefault) -> - // Occlusion signals come from the framework, and should interrupt any - // existing transition - val to = - if (isIdleOnCommunal || showCommunalByDefault) { - KeyguardState.GLANCEABLE_HUB - } else { - KeyguardState.LOCKSCREEN - } - startTransitionTo(to) + .sample( + communalInteractor.isIdleOnCommunal, + communalInteractor.showCommunalFromOccluded, + communalInteractor.dreamFromOccluded, + ) + .collect { (_, isIdleOnCommunal, showCommunalFromOccluded, dreamFromOccluded) -> + startTransitionToLockscreenOrHub( + isIdleOnCommunal, + showCommunalFromOccluded, + dreamFromOccluded + ) } } } else { @@ -106,27 +108,43 @@ constructor( .sample( keyguardInteractor.isKeyguardShowing, communalInteractor.isIdleOnCommunal, - communalInteractor.showByDefault, + communalInteractor.showCommunalFromOccluded, + communalInteractor.dreamFromOccluded, ) - .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _) -> + .filterRelevantKeyguardStateAnd { (isOccluded, isShowing, _, _, _) -> !isOccluded && isShowing } - .collect { (_, _, isIdleOnCommunal, showCommunalByDefault) -> - // Occlusion signals come from the framework, and should interrupt any - // existing transition - val to = - if (isIdleOnCommunal || showCommunalByDefault) { - 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 @@ -147,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/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index da4f85e0dd2f..7cee258dc39f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -36,9 +36,12 @@ import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch @SysUISingleton @@ -64,12 +67,7 @@ constructor( /** Current BlueprintId */ val blueprintId = - combine( - configurationInteractor.onAnyConfigurationChange, - fingerprintPropertyInteractor.propertiesInitialized.filter { it }, - clockInteractor.currentClock, - shadeInteractor.shadeMode, - ) { _, _, _, shadeMode -> + shadeInteractor.shadeMode.map { shadeMode -> val useSplitShade = shadeMode == ShadeMode.Split && !ComposeLockscreen.isEnabled when { useSplitShade -> SplitShadeKeyguardBlueprint.ID @@ -77,17 +75,29 @@ constructor( } } + private val refreshEvents: Flow<Unit> = + merge( + configurationInteractor.onAnyConfigurationChange, + fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map {}, + ) + init { applicationScope.launch { blueprintId.collect { transitionToBlueprint(it) } } + applicationScope.launch { refreshEvents.collect { refreshBlueprint() } } } /** - * Transitions to a blueprint. + * Transitions to a blueprint, or refreshes it if already applied. * * @param blueprintId * @return whether the transition has succeeded. */ - fun transitionToBlueprint(blueprintId: String): Boolean { + fun transitionOrRefreshBlueprint(blueprintId: String): Boolean { + if (blueprintId == blueprint.value.id) { + refreshBlueprint() + return true + } + return keyguardBlueprintRepository.applyBlueprint(blueprintId) } @@ -97,7 +107,7 @@ constructor( * @param blueprintId * @return whether the transition has succeeded. */ - fun transitionToBlueprint(blueprintId: Int): Boolean { + fun transitionToBlueprint(blueprintId: String): Boolean { return keyguardBlueprintRepository.applyBlueprint(blueprintId) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt index 08d29d4fc0a6..1aac1c5940b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt @@ -25,6 +25,9 @@ import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER +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.util.kotlin.sample import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -48,6 +51,7 @@ constructor( transitionInteractor: KeyguardTransitionInteractor, val dismissInteractor: KeyguardDismissInteractor, @Application private val applicationScope: CoroutineScope, + sceneInteractor: SceneInteractor, ) { val dismissAction: Flow<DismissAction> = repository.dismissAction @@ -72,7 +76,12 @@ constructor( ) private val finishedTransitionToGone: Flow<Unit> = - transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} // map to Unit + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) }.map {} + } else { + transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} + } + val executeDismissAction: Flow<() -> KeyguardDone> = merge( finishedTransitionToGone, 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 d19176853387..c44a40f33857 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 @@ -33,7 +33,6 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel -import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff @@ -64,10 +63,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 @@ -90,6 +89,7 @@ constructor( keyguardTransitionInteractor: KeyguardTransitionInteractor, sceneInteractorProvider: Provider<SceneInteractor>, private val fromGoneTransitionInteractor: Provider<FromGoneTransitionInteractor>, + private val fromLockscreenTransitionInteractor: Provider<FromLockscreenTransitionInteractor>, sharedNotificationContainerInteractor: Provider<SharedNotificationContainerInteractor>, @Application applicationScope: CoroutineScope, ) { @@ -98,6 +98,7 @@ constructor( /** Bounds of the notification container. */ val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy { + SceneContainerFlag.assertInLegacyMode() combine( _notificationPlaceholderBounds, sharedNotificationContainerInteractor.get().configurationBasedDimensions, @@ -116,6 +117,7 @@ constructor( } fun setNotificationContainerBounds(position: NotificationContainerBounds) { + SceneContainerFlag.assertInLegacyMode() _notificationPlaceholderBounds.value = position } @@ -179,13 +181,12 @@ constructor( isDreaming && isDozeOff(dozeTransitionModel.to) } .sample(powerInteractor.isAwake) { isAbleToDream, isAwake -> isAbleToDream && isAwake } - .flatMapLatest { isAbleToDream -> - flow { - delay(50) - emit(isAbleToDream) - } - } - .distinctUntilChanged() + .debounce(50L) + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) /** Whether the keyguard is showing or not. */ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState") @@ -229,18 +230,24 @@ constructor( @JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow /** Whether the alternate bouncer is showing or not. */ - val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible + val alternateBouncerShowing: Flow<Boolean> = + bouncerRepository.alternateBouncerVisible.sample(isAbleToDream) { + alternateBouncerVisible, + isAbleToDream -> + if (isAbleToDream) { + // If the alternate bouncer will show over a dream, it is likely that the dream has + // requested a dismissal, which will stop the dream. By delaying this slightly, the + // DREAMING->LOCKSCREEN transition will now happen first, followed by + // LOCKSCREEN->ALTERNATE_BOUNCER. + delay(600L) + } + alternateBouncerVisible + } /** Observable for the [StatusBarState] */ val statusBarState: Flow<StatusBarState> = repository.statusBarState - /** Source of the most recent biometric unlock, such as fingerprint or face. */ - val biometricUnlockSource: Flow<BiometricUnlockSource?> = repository.biometricUnlockSource - - /** - * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear, - * side, under display) is used to unlock the device. - */ + /** Observable for [BiometricUnlockModel] when biometrics are used to unlock the device. */ val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState /** Keyguard is present and is not occluded. */ @@ -307,24 +314,38 @@ constructor( configurationInteractor .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up) .flatMapLatest { translationDistance -> - combine( + combineTransform( shadeRepository.legacyShadeExpansion.onStart { emit(0f) }, keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) }, ) { legacyShadeExpansion, goneValue -> - if (goneValue == 1f || legacyShadeExpansion == 0f) { + val isLegacyShadeInResetPosition = + legacyShadeExpansion == 0f || legacyShadeExpansion == 1f + if (goneValue == 1f || (goneValue == 0f && isLegacyShadeInResetPosition)) { // Reset the translation value - 0f - } else { - // On swipe up, translate the keyguard to reveal the bouncer - MathUtils.lerp( - translationDistance, - 0, - Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(legacyShadeExpansion) + emit(0f) + } else if (!isLegacyShadeInResetPosition) { + // On swipe up, translate the keyguard to reveal the bouncer, OR a GONE + // transition is running, which means this is a swipe to dismiss. Values of + // 0f and 1f need to be ignored in the legacy shade expansion. These can + // flip arbitrarily as the legacy shade is reset, and would cause the + // translation value to jump around unexpectedly. + emit( + MathUtils.lerp( + translationDistance, + 0, + Interpolators.FAST_OUT_LINEAR_IN.getInterpolation( + legacyShadeExpansion + ), + ) ) } } } - .distinctUntilChanged() + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = 0f, + ) val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered @@ -417,6 +438,11 @@ constructor( fromGoneTransitionInteractor.get().showKeyguard() } + /** Temporary shim, until [KeyguardWmStateRefactor] is enabled */ + fun dismissKeyguard() { + fromLockscreenTransitionInteractor.get().dismissKeyguard() + } + companion object { private const val TAG = "KeyguardInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 1e3eac8440d0..ccce3bf1397c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -113,7 +113,8 @@ constructor( .transitionState .map { when (it) { - is ObservableTransitionState.Idle -> it.scene == Scenes.Lockscreen + is ObservableTransitionState.Idle -> + it.currentScene == Scenes.Lockscreen is ObservableTransitionState.Transition -> it.fromScene == Scenes.Lockscreen || it.toScene == Scenes.Lockscreen } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt index 20b7b2a91ade..82255a0c0d54 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt @@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart /** * Distance over which the surface behind the keyguard is animated in during a Y-translation @@ -102,8 +103,11 @@ constructor( */ private val isNotificationLaunchAnimationRunningOnKeyguard = notificationLaunchInteractor.isLaunchAnimationRunning - .sample(transitionInteractor.finishedKeyguardState) - .map { it != KeyguardState.GONE } + .sample(transitionInteractor.finishedKeyguardState, ::Pair) + .map { (animationRunning, finishedState) -> + animationRunning && finishedState != KeyguardState.GONE + } + .onStart { emit(false) } /** * Whether we're animating the surface, or a notification launch animation is running (which diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index e711edc0c302..cf6942e2f245 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.keyguard.logging.KeyguardLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -26,6 +27,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.launch private val TAG = KeyguardTransitionAuditLogger::class.simpleName!! @@ -41,6 +43,7 @@ constructor( private val logger: KeyguardLogger, private val powerInteractor: PowerInteractor, private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel, + private val keyguardRootViewModel: KeyguardRootViewModel, private val shadeInteractor: ShadeInteractor, private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) { @@ -53,12 +56,6 @@ constructor( } scope.launch { - sharedNotificationContainerViewModel - .getMaxNotifications { height, useExtraShelfSpace -> height.toInt() } - .collect { logger.log(TAG, VERBOSE, "Notif: max height in px", it) } - } - - scope.launch { sharedNotificationContainerViewModel.isOnLockscreen.collect { logger.log(TAG, VERBOSE, "Notif: isOnLockscreen", it) } @@ -72,8 +69,8 @@ constructor( if (!SceneContainerFlag.isEnabled) { scope.launch { - sharedNotificationContainerViewModel.bounds.collect { - logger.log(TAG, VERBOSE, "Notif: bounds", it) + sharedNotificationContainerViewModel.bounds.debounce(20L).collect { + logger.log(TAG, VERBOSE, "Notif: bounds (debounced)", it) } } } @@ -113,6 +110,18 @@ constructor( } scope.launch { + keyguardInteractor.keyguardTranslationY.collect { + logger.log(TAG, VERBOSE, "keyguardTranslationY", it) + } + } + + scope.launch { + keyguardRootViewModel.burnInModel.debounce(20L).collect { + logger.log(TAG, VERBOSE, "BurnInModel (debounced)", it) + } + } + + scope.launch { keyguardInteractor.isKeyguardDismissible.collect { logger.log(TAG, VERBOSE, "isDismissible", it) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt new file mode 100644 index 000000000000..5ad7762bb512 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.domain.interactor + +import android.util.Log +import com.android.systemui.CoreStartable +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.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +/** Handles initialization of the KeyguardTransitionRepository on boot. */ +@SysUISingleton +class KeyguardTransitionBootInteractor +@Inject +constructor( + @Application val scope: CoroutineScope, + val deviceEntryInteractor: DeviceEntryInteractor, + val deviceProvisioningInteractor: DeviceProvisioningInteractor, + val keyguardTransitionInteractor: KeyguardTransitionInteractor, + val repository: KeyguardTransitionRepository, +) : CoreStartable { + + /** + * Whether the lockscreen should be showing when the device starts up for the first time. If not + * then we'll seed the repository with a transition from OFF -> GONE. + */ + @OptIn(ExperimentalCoroutinesApi::class) + private val showLockscreenOnBoot = + deviceProvisioningInteractor.isDeviceProvisioned.map { provisioned -> + (provisioned || deviceEntryInteractor.isAuthenticationRequired()) && + deviceEntryInteractor.isLockscreenEnabled() + } + + override fun start() { + scope.launch { + val state = + if (showLockscreenOnBoot.first()) { + KeyguardState.LOCKSCREEN + } else { + KeyguardState.GONE + } + + if ( + keyguardTransitionInteractor.currentTransitionInfoInternal.value.from != + KeyguardState.OFF + ) { + Log.e( + "KeyguardTransitionInteractor", + "showLockscreenOnBoot emitted, but we've already " + + "transitioned to a state other than OFF. We'll respect that " + + "transition, but this should not happen." + ) + } else { + repository.emitInitialStepsFromOff(state) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt index 91f8420393e1..31b0bf7fe425 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt @@ -27,6 +27,7 @@ class KeyguardTransitionCoreStartable constructor( private val interactors: Set<TransitionInteractor>, private val auditLogger: KeyguardTransitionAuditLogger, + private val bootInteractor: KeyguardTransitionBootInteractor, ) : CoreStartable { override fun start() { @@ -51,6 +52,7 @@ constructor( it.start() } auditLogger.start() + bootInteractor.start() } companion object { 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..c65dc305b0cc 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,7 +17,10 @@ package com.android.systemui.keyguard.domain.interactor +import android.annotation.FloatRange +import android.annotation.SuppressLint import android.util.Log +import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -28,12 +31,16 @@ 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.KeyguardState.UNDEFINED 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.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +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.ExperimentalCoroutinesApi @@ -68,14 +75,17 @@ constructor( private val fromAlternateBouncerTransitionInteractor: dagger.Lazy<FromAlternateBouncerTransitionInteractor>, private val fromDozingTransitionInteractor: dagger.Lazy<FromDozingTransitionInteractor>, + private val sceneInteractor: dagger.Lazy<SceneInteractor>, ) { - private val transitionMap = mutableMapOf<Edge, MutableSharedFlow<TransitionStep>>() + private val transitionMap = mutableMapOf<Edge.StateToState, MutableSharedFlow<TransitionStep>>() /** * Numerous flows are derived from, or care directly about, the transition value in and out of a * 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 +100,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 +112,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() @@ -119,11 +133,11 @@ constructor( scope.launch { repository.transitions.collect { // FROM->TO - transitionMap[Edge(it.from, it.to)]?.emit(it) + transitionMap[Edge.create(it.from, it.to)]?.emit(it) // FROM->(ANY) - transitionMap[Edge(it.from, null)]?.emit(it) + transitionMap[Edge.create(it.from, null)]?.emit(it) // (ANY)->TO - transitionMap[Edge(null, it.to)]?.emit(it) + transitionMap[Edge.create(null, it.to)]?.emit(it) } } @@ -143,25 +157,70 @@ constructor( } } - /** Given an [edge], return a SharedFlow to collect only relevant [TransitionStep]. */ - fun getOrCreateFlow(edge: Edge): MutableSharedFlow<TransitionStep> { - return transitionMap.getOrPut(edge) { - MutableSharedFlow<TransitionStep>( - extraBufferCapacity = 10, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) + fun transition(edge: Edge, edgeWithoutSceneContainer: Edge): Flow<TransitionStep> { + return transition(if (SceneContainerFlag.isEnabled) edge else edgeWithoutSceneContainer) + } + + /** Given an [edge], return a Flow to collect only relevant [TransitionStep]s. */ + @SuppressLint("SharedFlowCreation") + fun transition(edge: Edge): Flow<TransitionStep> { + edge.verifyValidKeyguardStates() + val mappedEdge = getMappedEdge(edge) + + val flow: Flow<TransitionStep> = + transitionMap.getOrPut(mappedEdge) { + MutableSharedFlow( + extraBufferCapacity = 10, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + } + + return if (SceneContainerFlag.isEnabled) { + flow.filter { + val fromScene = + when (edge) { + is Edge.StateToState -> edge.from?.mapToSceneContainerScene() + is Edge.StateToScene -> edge.from.mapToSceneContainerScene() + is Edge.SceneToState -> edge.from + } + + val toScene = + when (edge) { + is Edge.StateToState -> edge.to?.mapToSceneContainerScene() + is Edge.StateToScene -> edge.to + is Edge.SceneToState -> edge.to.mapToSceneContainerScene() + } + + fun SceneKey?.isLockscreenOrNull() = this == Scenes.Lockscreen || this == null + + return@filter (fromScene.isLockscreenOrNull() && toScene.isLockscreenOrNull()) || + sceneInteractor.get().transitionState.value.isTransitioning(fromScene, toScene) + } + } else { + flow } } /** - * Receive all [TransitionStep] matching a filter of [from]->[to]. Allow nulls in order to match - * any transition, for instance (any)->GONE. + * Converts old KTF states to UNDEFINED when [SceneContainerFlag] is enabled. + * + * Does nothing otherwise. + * + * This method should eventually be removed when new code is only written for scene container. + * Even when all edges are ported today, there is still development on going in production that + * might utilize old states. */ - fun transition(from: KeyguardState? = null, to: KeyguardState? = null): Flow<TransitionStep> { - if (from == null && to == null) { - throw IllegalArgumentException("from and to cannot both be null") + private fun getMappedEdge(edge: Edge): Edge.StateToState { + if (!SceneContainerFlag.isEnabled) return edge as Edge.StateToState + return when (edge) { + is Edge.StateToState -> + Edge.create( + from = edge.from?.mapToSceneContainerState(), + to = edge.to?.mapToSceneContainerState() + ) + is Edge.SceneToState -> Edge.create(UNDEFINED, edge.to) + is Edge.StateToScene -> Edge.create(edge.from, UNDEFINED) } - return getOrCreateFlow(Edge(from = from, to = to)) } /** @@ -180,6 +239,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 +261,22 @@ 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) + val currentTransitionInfo: StateFlow<TransitionInfo> = repository.currentTransitionInfoInternal + + /** 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 +314,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 } @@ -344,30 +416,37 @@ constructor( val isInTransitionToAnyState = isInTransitionWhere({ true }, { true }) fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> { - return getOrCreateFlow(Edge(from = fromState, to = null)) + return transition(Edge.create(from = fromState, to = null)) } fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> { - return getOrCreateFlow(Edge(from = null, to = toState)) + return transition(Edge.create(from = null, to = toState)) } /** * Called to start a transition that will ultimately dismiss the keyguard from the current * state. + * + * This is called exclusively by sources that can authoritatively say we should be unlocked, + * including KeyguardSecurityContainerController and WindowManager. */ - fun startDismissKeyguardTransition() { - when (val startedState = startedKeyguardState.replayCache.last()) { + fun startDismissKeyguardTransition(reason: String = "") { + // TODO(b/336576536): Check if adaptation for scene framework is needed + if (SceneContainerFlag.isEnabled) return + Log.d(TAG, "#startDismissKeyguardTransition(reason=$reason)") + when (val startedState = currentTransitionInfoInternal.value.to) { LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard() PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer() ALTERNATE_BOUNCER -> fromAlternateBouncerTransitionInteractor.get().dismissAlternateBouncer() AOD -> fromAodTransitionInteractor.get().dismissAod() DOZING -> fromDozingTransitionInteractor.get().dismissFromDozing() - else -> - Log.e( - "KeyguardTransitionInteractor", - "We don't know how to dismiss keyguard from state $startedState." + KeyguardState.GONE -> + Log.i( + TAG, + "Already transitioning to GONE; ignoring startDismissKeyguardTransition." ) + else -> Log.e(TAG, "We don't know how to dismiss keyguard from state $startedState.") } } @@ -375,7 +454,7 @@ constructor( fun isInTransitionToState( state: KeyguardState, ): Flow<Boolean> { - return getOrCreateFlow(Edge(from = null, to = state)) + return transition(Edge.create(from = null, to = state)) .mapLatest { it.transitionState.isTransitioning() } .onStart { emit(false) } .distinctUntilChanged() @@ -384,12 +463,16 @@ constructor( /** * Whether we're in a transition to and from the given [KeyguardState]s, but haven't yet * completed it. + * + * Provide [edgeWithoutSceneContainer] when the edge is different from what it is without it. If + * the edges are equal before and after the flag it is sufficient to provide just [edge]. */ - fun isInTransition( - from: KeyguardState, - to: KeyguardState, - ): Flow<Boolean> { - return getOrCreateFlow(Edge(from = from, to = to)) + fun isInTransition(edge: Edge, edgeWithoutSceneContainer: Edge? = null): Flow<Boolean> { + return if (SceneContainerFlag.isEnabled) { + transition(edge) + } else { + transition(edgeWithoutSceneContainer ?: edge) + } .mapLatest { it.transitionState.isTransitioning() } .onStart { emit(false) } .distinctUntilChanged() @@ -401,7 +484,7 @@ constructor( fun isInTransitionFromState( state: KeyguardState, ): Flow<Boolean> { - return getOrCreateFlow(Edge(from = state, to = null)) + return transition(Edge.create(from = state, to = null)) .mapLatest { it.transitionState.isTransitioning() } .onStart { emit(false) } .distinctUntilChanged() @@ -452,7 +535,7 @@ constructor( * If you only care about a single state for both from and to, instead use the optimized * [isInTransition]. */ - fun isInTransitionWhere( + private fun isInTransitionWhere( fromToStatePredicate: (KeyguardState, KeyguardState) -> Boolean ): Flow<Boolean> { return repository.transitions @@ -489,7 +572,23 @@ 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) + + companion object { + private val TAG = KeyguardTransitionInteractor::class.simpleName + } } 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/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index b2a24ca85b07..323ceef06a97 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -229,6 +229,7 @@ sealed class TransitionInteractor( startTransitionTo( toState = KeyguardState.OCCLUDED, modeOnCanceled = TransitionModeOnCanceled.RESET, + ownerReason = "keyguardInteractor.onCameraLaunchDetected", ) } } 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 8d02e0efd72d..350527a85e18 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 @@ -14,14 +14,21 @@ * limitations under the License. */ +@file:OptIn(ExperimentalCoroutinesApi::class) + package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +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.sample +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -42,6 +49,8 @@ constructor( fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor, fromAlternateBouncerInteractor: FromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor: NotificationLaunchAnimationInteractor, + sceneInteractor: Lazy<SceneInteractor>, + deviceEntryInteractor: Lazy<DeviceEntryInteractor>, ) { private val defaultSurfaceBehindVisibility = transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible) @@ -103,21 +112,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.get().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 +157,32 @@ 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) { + deviceEntryInteractor.get().isDeviceEntered.map { !it } + } 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) + 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() + .distinctUntilChanged() + } /** * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window @@ -165,7 +199,7 @@ constructor( ) { isDozing, biometricUnlockState -> // AOD is visible if we're dozing, unless we are wake and unlocking (where we go // directly from AOD to unlocked while dozing). - isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState) + isDozing && !BiometricUnlockMode.isWakeAndUnlock(biometricUnlockState.mode) } .distinctUntilChanged() 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..3baeb7682e12 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractor.kt @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package 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.currentTransitionInfo.value.to, + 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.currentTransitionInfo.value.to + 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) { + // TODO(b/330311871): This is based on a sharedFlow and thus might not be up-to-date and + // cause a race condition. (There is no known scenario that is currently affected.) + 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.currentTransitionInfo.value.to + 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/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt index 2ae5ce1ad545..4c709fbf4d9f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt @@ -15,8 +15,19 @@ */ package com.android.systemui.keyguard.shared.model +/** + * Model [BiometricUnlockMode] with [BiometricUnlockSource]. + * + * @param source can be null as a starting state or if the unlock isn't coming from a biometric (the + * latter should be deprecated in the future, b/338578036) + */ +data class BiometricUnlockModel( + val mode: BiometricUnlockMode, + val source: BiometricUnlockSource?, +) + /** Model device wakefulness states. */ -enum class BiometricUnlockModel { +enum class BiometricUnlockMode { /** Mode in which we don't need to wake up the device when we authenticate. */ NONE, /** @@ -60,12 +71,12 @@ enum class BiometricUnlockModel { DISMISS_BOUNCER ) - fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean { - return wakeAndUnlockModes.contains(model) + fun isWakeAndUnlock(mode: BiometricUnlockMode): Boolean { + return wakeAndUnlockModes.contains(mode) } - fun dismissesKeyguard(model: BiometricUnlockModel): Boolean { - return dismissesKeyguardModes.contains(model) + fun dismissesKeyguard(mode: BiometricUnlockMode): Boolean { + return dismissesKeyguardModes.contains(mode) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt index a0f9be629132..4f516f586216 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/Edge.kt @@ -15,8 +15,98 @@ */ package com.android.systemui.keyguard.shared.model -/** FROM -> TO keyguard transition. null values are allowed to signify FROM -> *, or * -> TO */ -data class Edge( - val from: KeyguardState?, - val to: KeyguardState?, -) +import android.util.Log +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.keyguard.shared.model.KeyguardState.UNDEFINED +import com.android.systemui.scene.shared.flag.SceneContainerFlag + +/** + * Represents an edge either between two Keyguard Transition Framework states (KTF) or a KTF state + * and a scene container scene. Passing [null] in either [from] or [to] indicates a wildcard. + * + * Wildcards are not allowed for transitions involving a scene. Use [sceneInteractor] directly + * instead. Reason: [TransitionStep]s are not emitted for every edge leading into/out of a scene. + * For example: Lockscreen -> Gone would be emitted as LOCKSCREEN -> UNDEFINED but Bouncer -> Gone + * would not emit anything. + */ +sealed class Edge { + + fun verifyValidKeyguardStates() { + when (this) { + is StateToState -> verifyValidKeyguardStates(from, to) + is SceneToState -> verifyValidKeyguardStates(null, to) + is StateToScene -> verifyValidKeyguardStates(from, null) + } + } + + private fun verifyValidKeyguardStates(from: KeyguardState?, to: KeyguardState?) { + val mappedFrom = from?.mapToSceneContainerState() + val mappedTo = to?.mapToSceneContainerState() + + val fromChanged = from != mappedFrom + val toChanged = to != mappedTo + + if (SceneContainerFlag.isEnabled) { + if (fromChanged && toChanged) { + // TODO:(b/330311871) As we come close to having all current edges converted these + // error messages can be converted to throw such that future developers fail early + // when they introduce invalid edges. + Log.e( + TAG, + """ + The edge ${from?.name} => ${to?.name} was automatically converted to + ${mappedFrom?.name} => ${mappedTo?.name} but does not exist anymore in KTF. + Please remove or port this edge to scene container.""" + .trimIndent(), + ) + } else if ((fromChanged && to == null) || (toChanged && from == null)) { + Log.e( + TAG, + """ + The edge ${from?.name} => ${to?.name} was automatically converted to + ${mappedFrom?.name} => ${mappedTo?.name}. Wildcards are not allowed together + with UNDEFINED because it will only be tracking edges leading in and out of + the Lockscreen scene but miss others. Please remove or port this edge.""" + .trimIndent(), + Exception() + ) + } else if (fromChanged || toChanged) { + Log.w( + TAG, + """ + The edge ${from?.name} => ${to?.name} was automatically converted to + ${mappedFrom?.name} => ${mappedTo?.name} it probably exists but needs explicit + conversion. Please remove or port this edge to scene container.""" + .trimIndent(), + ) + } + } else { + if (from == UNDEFINED || to == UNDEFINED) { + Log.e( + TAG, + "UNDEFINED should not be used when scene container is disabled", + ) + } + } + } + + data class StateToState(val from: KeyguardState?, val to: KeyguardState?) : Edge() { + init { + check(!(from == null && to == null)) { "to and from can't both be null" } + } + } + + data class StateToScene(val from: KeyguardState, val to: SceneKey) : Edge() + + data class SceneToState(val from: SceneKey, val to: KeyguardState) : Edge() + + companion object { + private const val TAG = "Edge" + + fun create(from: KeyguardState? = null, to: KeyguardState? = null) = StateToState(from, to) + + fun create(from: KeyguardState, to: SceneKey) = StateToScene(from, to) + + fun create(from: SceneKey, to: KeyguardState) = SceneToState(from, to) + } +} 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..6a2bb5f51c5d 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 @@ -15,9 +15,12 @@ */ package com.android.systemui.keyguard.shared.model +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.scene.shared.model.Scenes + /** 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 +32,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 +50,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,17 +71,56 @@ 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; + fun mapToSceneContainerState(): KeyguardState { + return when (this) { + OFF, + DOZING, + DREAMING, + DREAMING_LOCKSCREEN_HOSTED, + AOD, + ALTERNATE_BOUNCER, + OCCLUDED, + LOCKSCREEN -> this + GLANCEABLE_HUB, + PRIMARY_BOUNCER, + GONE, + UNDEFINED -> UNDEFINED + } + } + + fun mapToSceneContainerScene(): SceneKey? { + return when (this) { + OFF, + DOZING, + DREAMING, + DREAMING_LOCKSCREEN_HOSTED, + AOD, + ALTERNATE_BOUNCER, + OCCLUDED, + LOCKSCREEN -> Scenes.Lockscreen + GLANCEABLE_HUB -> Scenes.Communal + PRIMARY_BOUNCER -> Scenes.Bouncer + GONE -> Scenes.Gone + UNDEFINED -> null + } + } + companion object { /** Whether the lockscreen is visible when we're FINISHED in the given state. */ @@ -109,6 +151,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/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt index 735b10907c73..23aa21cfbf31 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.scene.shared.flag.SceneContainerFlag import javax.inject.Inject import kotlin.math.max import kotlin.math.min @@ -52,20 +53,20 @@ constructor( /** Invoke once per transition between FROM->TO states to get access to a shared flow. */ fun setup( duration: Duration, - from: KeyguardState?, - to: KeyguardState?, + edge: Edge, ): FlowBuilder { - if (from == null && to == null) { - throw IllegalArgumentException("from and to are both null") - } - - return FlowBuilder(duration, Edge(from, to)) + return FlowBuilder(duration, edge) } inner class FlowBuilder( private val transitionDuration: Duration, private val edge: Edge, ) { + fun setupWithoutSceneContainer(edge: Edge.StateToState): FlowBuilder { + if (SceneContainerFlag.isEnabled) return this + return setup(this.transitionDuration, edge) + } + /** * Transitions will occur over a [transitionDuration] with [TransitionStep]s being emitted * in the range of [0, 1]. View animations should begin and end within a subset of this @@ -117,7 +118,7 @@ constructor( if (!duration.isPositive()) { throw IllegalArgumentException("duration must be a positive number: $duration") } - if ((startTime + duration).compareTo(transitionDuration) > 0) { + if ((startTime + duration) > transitionDuration) { throw IllegalArgumentException( "startTime($startTime) + duration($duration) must be" + " <= transitionDuration($transitionDuration)" @@ -153,7 +154,7 @@ constructor( } return transitionInteractor - .getOrCreateFlow(edge) + .transition(edge) .map { step -> StateToValue( from = step.from, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 53f013275451..6550937d2db0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.binder import android.graphics.PixelFormat +import android.util.Log import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -95,11 +96,11 @@ constructor( applicationScope.launch("$TAG#alternateBouncerWindowViewModel") { alternateBouncerWindowViewModel.get().alternateBouncerWindowRequired.collect { addAlternateBouncerWindowView -> + Log.d(TAG, "alternateBouncerWindowRequired=$addAlternateBouncerWindowView") if (addAlternateBouncerWindowView) { addViewToWindowManager() - val scrim = + val scrim: ScrimView = alternateBouncerView!!.requireViewById(R.id.alternate_bouncer_scrim) - as ScrimView scrim.viewAlpha = 0f bind(alternateBouncerView!!, alternateBouncerDependencies.get()) } else { @@ -187,23 +188,30 @@ constructor( view.repeatWhenAttached { alternateBouncerViewContainer -> repeatOnLifecycle(Lifecycle.State.STARTED) { launch("$TAG#viewModel.registerForDismissGestures") { - viewModel.registerForDismissGestures.collect { registerForDismissGestures -> - if (registerForDismissGestures) { - swipeUpAnywhereGestureHandler.addOnGestureDetectedCallback(swipeTag) { _ - -> - alternateBouncerDependencies.powerInteractor.onUserTouch() - viewModel.showPrimaryBouncer() - } - tapGestureDetector.addOnGestureDetectedCallback(tapTag) { _ -> - alternateBouncerDependencies.powerInteractor.onUserTouch() - viewModel.showPrimaryBouncer() + viewModel.registerForDismissGestures.collect { registerForDismissGestures -> + if (registerForDismissGestures) { + swipeUpAnywhereGestureHandler.addOnGestureDetectedCallback( + swipeTag + ) { _ -> + alternateBouncerDependencies.powerInteractor.onUserTouch() + viewModel.showPrimaryBouncer() + } + tapGestureDetector.addOnGestureDetectedCallback(tapTag) { _ -> + alternateBouncerDependencies.powerInteractor.onUserTouch() + viewModel.showPrimaryBouncer() + } + } else { + swipeUpAnywhereGestureHandler.removeOnGestureDetectedCallback( + swipeTag + ) + tapGestureDetector.removeOnGestureDetectedCallback(tapTag) } - } else { - swipeUpAnywhereGestureHandler.removeOnGestureDetectedCallback(swipeTag) - tapGestureDetector.removeOnGestureDetectedCallback(tapTag) } } - } + .invokeOnCompletion { + swipeUpAnywhereGestureHandler.removeOnGestureDetectedCallback(swipeTag) + tapGestureDetector.removeOnGestureDetectedCallback(tapTag) + } launch("$TAG#viewModel.scrimAlpha") { viewModel.scrimAlpha.collect { scrim.viewAlpha = it } 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 b0d45edaf8c2..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 @@ -34,6 +36,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,6 +63,7 @@ object DeviceEntryIconViewBinder { bgViewModel: DeviceEntryBackgroundViewModel, falsingManager: FalsingManager, vibratorHelper: VibratorHelper, + overrideColor: Color? = null, ) { DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() val longPressHandlingView = view.longPressHandlingView @@ -75,7 +79,7 @@ object DeviceEntryIconViewBinder { view, HapticFeedbackConstants.CONFIRM, ) - applicationScope.launch { viewModel.onLongPress() } + applicationScope.launch { viewModel.onUserInteraction() } } } @@ -94,9 +98,38 @@ object DeviceEntryIconViewBinder { longPressHandlingView.setLongPressHandlingEnabled(isEnabled) } } + launch("$TAG#viewModel.isUdfpsSupported") { + viewModel.isUdfpsSupported.collect { udfpsSupported -> + longPressHandlingView.longPressDuration = + if (udfpsSupported) { + { + view.resources + .getInteger(R.integer.config_udfpsDeviceEntryIconLongPress) + .toLong() + } + } else { + { + view.resources + .getInteger(R.integer.config_lockIconLongPress) + .toLong() + } + } + } + } 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") { @@ -132,9 +165,14 @@ object DeviceEntryIconViewBinder { view.getIconState(viewModel.type, viewModel.useAodVariant), /* merge */ false ) - fgIconView.contentDescription = - fgIconView.resources.getString(viewModel.type.contentDescriptionResId) - fgIconView.imageTintList = ColorStateList.valueOf(viewModel.tint) + if (viewModel.type.contentDescriptionResId != -1) { + fgIconView.contentDescription = + fgIconView.resources.getString( + viewModel.type.contentDescriptionResId + ) + } + 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..f2821a0f49d2 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,7 +33,9 @@ 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.launch @@ -41,6 +43,7 @@ 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 +51,7 @@ object KeyguardClockViewBinder { viewModel: KeyguardClockViewModel, keyguardClockInteractor: KeyguardClockInteractor, blueprintInteractor: KeyguardBlueprintInteractor, + rootViewModel: KeyguardRootViewModel, ) { keyguardRootView.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { @@ -105,6 +109,21 @@ object KeyguardClockViewBinder { } } } + + launch { + if (!MigrateClocksToBlueprint.isEnabled) return@launch + rootViewModel.burnInModel.collect { burnInModel -> + viewModel.currentClock.value?.let { + it.largeClock.layout.applyAodBurnIn( + AodClockBurnInModel( + translationX = burnInModel.translationX.toFloat(), + translationY = burnInModel.translationY.toFloat(), + scale = burnInModel.scale + ) + ) + } + } + } } } } @@ -117,17 +136,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 +166,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/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index abd79ab793d5..b9a79dccf76b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -118,6 +118,7 @@ object KeyguardQuickAffordanceViewBinder { } override fun destroy() { + view.setOnApplyWindowInsetsListener(null) disposableHandle.dispose() } } 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 ccc48b5ecb12..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 @@ -36,7 +36,6 @@ import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.systemui.Flags.newAodTransition @@ -237,7 +236,8 @@ object KeyguardRootViewBinder { indicationArea, startButton, endButton, - lockIcon -> { + lockIcon, + deviceEntryIcon -> { // Do not move these views } else -> childView.translationX = px @@ -257,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 } } } @@ -597,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 a8e9041abfd7..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,7 +43,9 @@ 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 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToDozingTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel @@ -196,6 +201,12 @@ abstract class DeviceEntryIconTransitionModule { @Binds @IntoSet + abstract fun offToLockscreen( + impl: OffToLockscreenTransitionViewModel + ): DeviceEntryIconTransition + + @Binds + @IntoSet abstract fun primaryBouncerToAod( impl: PrimaryBouncerToAodTransitionViewModel ): DeviceEntryIconTransition @@ -211,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 2735aed3ba4d..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 @@ -40,10 +40,7 @@ constructor( attrs: AttributeSet?, defStyleAttrs: Int = 0, ) : FrameLayout(context, attrs, defStyleAttrs) { - val longPressHandlingView: LongPressHandlingView = - LongPressHandlingView(context, attrs) { - context.resources.getInteger(R.integer.config_lockIconLongPress).toLong() - } + val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } val aodFpDrawable: LottieDrawable = LottieDrawable() @@ -68,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( @@ -214,7 +211,7 @@ constructor( R.id.unlocked, R.id.locked_aod, context.getDrawable(R.drawable.unlocked_to_aod_lock) as AnimatedVectorDrawable, - /* reversible */ true, + /* reversible */ false, ) } @@ -252,6 +249,7 @@ constructor( IconType.LOCK -> lockIconState[0] = android.R.attr.state_first IconType.UNLOCK -> lockIconState[0] = android.R.attr.state_last IconType.FINGERPRINT -> lockIconState[0] = android.R.attr.state_middle + IconType.NONE -> return StateSet.NOTHING } if (aod) { lockIconState[1] = android.R.attr.state_single @@ -265,6 +263,7 @@ constructor( LOCK(R.string.accessibility_lock_icon), UNLOCK(R.string.accessibility_unlock_button), FINGERPRINT(R.string.accessibility_fingerprint_label), + NONE(-1), } enum class AccessibilityHintType { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt index ce7ec0e22f1c..962cdf10cf86 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt @@ -46,15 +46,14 @@ constructor( return } - if ( - arg.isDigitsOnly() && keyguardBlueprintInteractor.transitionToBlueprint(arg.toInt()) - ) { - pw.println("Transition succeeded!") - } else if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) { - pw.println("Transition succeeded!") - } else { - pw.println("Invalid argument! To see available blueprint ids, run:") - pw.println("$ adb shell cmd statusbar blueprint help") + when { + arg.isDigitsOnly() -> pw.println("Invalid argument! Use string ids.") + keyguardBlueprintInteractor.transitionOrRefreshBlueprint(arg) -> + pw.println("Transition succeeded!") + else -> { + pw.println("Invalid argument! To see available blueprint ids, run:") + pw.println("$ adb shell cmd statusbar blueprint help") + } } } 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/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 29041d1665c3..0b8376af811c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -21,6 +21,7 @@ import android.content.Context import android.graphics.Point import android.graphics.Rect import android.util.DisplayMetrics +import android.util.Log import android.view.View import android.view.WindowManager import androidx.annotation.VisibleForTesting @@ -116,6 +117,10 @@ constructor( override fun applyConstraints(constraintSet: ConstraintSet) { val isUdfpsSupported = if (DeviceEntryUdfpsRefactor.isEnabled) { + Log.d( + "DefaultDeviceEntrySection", + "isUdfpsSupported=${deviceEntryIconViewModel.get().isUdfpsSupported.value}" + ) deviceEntryIconViewModel.get().isUdfpsSupported.value } else { authController.isUdfpsSupported @@ -138,8 +143,24 @@ constructor( val iconRadiusPx = (defaultDensity * 36).toInt() if (isUdfpsSupported) { - authController.udfpsLocation?.let { udfpsLocation -> - centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet) + if (DeviceEntryUdfpsRefactor.isEnabled) { + deviceEntryIconViewModel.get().udfpsLocation.value?.let { udfpsLocation -> + Log.d( + "DeviceEntrySection", + "udfpsLocation=$udfpsLocation" + + " unusedAuthController=${authController.udfpsLocation}" + ) + centerIcon( + Point(udfpsLocation.centerX.toInt(), udfpsLocation.centerY.toInt()), + udfpsLocation.radius, + constraintSet + ) + } + } else { + authController.udfpsLocation?.let { udfpsLocation -> + Log.d("DeviceEntrySection", "udfpsLocation=$udfpsLocation") + centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet) + } } } else { centerIcon( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt index 45b82576c6c4..9146c605ab63 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.res.Resources +import android.view.WindowInsets import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet.BOTTOM @@ -25,15 +26,19 @@ import androidx.constraintlayout.widget.ConstraintSet.LEFT import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.RIGHT import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE +import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.KeyguardBottomAreaRefactor +import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder +import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper +import dagger.Lazy import javax.inject.Inject class DefaultShortcutsSection @@ -46,11 +51,29 @@ constructor( private val falsingManager: FalsingManager, private val indicationController: KeyguardIndicationController, private val vibratorHelper: VibratorHelper, + private val keyguardBlueprintInteractor: Lazy<KeyguardBlueprintInteractor>, ) : BaseShortcutSection() { + + // Amount to increase the bottom margin by to avoid colliding with inset + private var safeInsetBottom = 0 + override fun addViews(constraintLayout: ConstraintLayout) { if (KeyguardBottomAreaRefactor.isEnabled) { addLeftShortcut(constraintLayout) addRightShortcut(constraintLayout) + + constraintLayout + .requireViewById<LaunchableImageView>(R.id.start_button) + .setOnApplyWindowInsetsListener { _, windowInsets -> + val tempSafeInset = windowInsets?.displayCutout?.safeInsetBottom ?: 0 + if (safeInsetBottom != tempSafeInset) { + safeInsetBottom = tempSafeInset + keyguardBlueprintInteractor + .get() + .refreshBlueprint(IntraBlueprintTransition.Type.DefaultTransition) + } + WindowInsets.CONSUMED + } } } @@ -91,12 +114,24 @@ constructor( constrainWidth(R.id.start_button, width) constrainHeight(R.id.start_button, height) connect(R.id.start_button, LEFT, PARENT_ID, LEFT, horizontalOffsetMargin) - connect(R.id.start_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin) + connect( + R.id.start_button, + BOTTOM, + PARENT_ID, + BOTTOM, + verticalOffsetMargin + safeInsetBottom + ) constrainWidth(R.id.end_button, width) constrainHeight(R.id.end_button, height) connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin) - connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin) + connect( + R.id.end_button, + BOTTOM, + PARENT_ID, + BOTTOM, + verticalOffsetMargin + safeInsetBottom + ) // The constraint set visibility for start and end button are default visible, set to // ignore so the view's own initial visibility (invisible) is used diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt index 7c29b39ad12c..7c745bc227f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt @@ -38,6 +38,8 @@ import com.android.systemui.shared.R as sharedR import com.google.android.material.math.MathUtils import kotlin.math.abs +internal fun View.getRect(): Rect = Rect(this.left, this.top, this.right, this.bottom) + internal fun View.setRect(rect: Rect) = this.setLeftTopRightBottom(rect.left, rect.top, rect.right, rect.bottom) @@ -55,9 +57,9 @@ class ClockSizeTransition( addTransition(SmartspaceMoveTransition(config, clockViewModel)) } - open class VisibilityBoundsTransition() : Transition() { - var captureSmartspace: Boolean = false - + abstract class VisibilityBoundsTransition() : Transition() { + abstract val captureSmartspace: Boolean + protected val TAG = this::class.simpleName!! override fun captureEndValues(transition: TransitionValues) = captureValues(transition) override fun captureStartValues(transition: TransitionValues) = captureValues(transition) override fun getTransitionProperties(): Array<String> = TRANSITION_PROPERTIES @@ -66,12 +68,18 @@ class ClockSizeTransition( val view = transition.view transition.values[PROP_VISIBILITY] = view.visibility transition.values[PROP_ALPHA] = view.alpha - transition.values[PROP_BOUNDS] = Rect(view.left, view.top, view.right, view.bottom) + transition.values[PROP_BOUNDS] = view.getRect() if (!captureSmartspace) return - val ss = (view.parent as View).findViewById<View>(sharedR.id.bc_smartspace_view) - if (ss == null) return - transition.values[SMARTSPACE_BOUNDS] = Rect(ss.left, ss.top, ss.right, ss.bottom) + val parent = view.parent as View + val targetSSView = + parent.findViewById<View>(sharedR.id.bc_smartspace_view) + ?: parent.findViewById<View>(R.id.keyguard_slice_view) + if (targetSSView == null) { + Log.e(TAG, "Failed to find smartspace equivalent target under $parent") + return + } + transition.values[SMARTSPACE_BOUNDS] = targetSSView.getRect() } open fun mutateBounds( @@ -89,20 +97,24 @@ class ClockSizeTransition( startValues: TransitionValues?, endValues: TransitionValues? ): Animator? { - if (startValues == null || endValues == null) return null + if (startValues == null || endValues == null) { + Log.w( + TAG, + "Couldn't create animator: startValues=$startValues; endValues=$endValues" + ) + return null + } var fromVis = startValues.values[PROP_VISIBILITY] as Int var fromIsVis = fromVis == View.VISIBLE var fromAlpha = startValues.values[PROP_ALPHA] as Float val fromBounds = startValues.values[PROP_BOUNDS] as Rect - val fromSSBounds = - if (captureSmartspace) startValues.values[SMARTSPACE_BOUNDS] as Rect else null + val fromSSBounds = startValues.values[SMARTSPACE_BOUNDS] as Rect? val toView = endValues.view val toVis = endValues.values[PROP_VISIBILITY] as Int val toBounds = endValues.values[PROP_BOUNDS] as Rect - val toSSBounds = - if (captureSmartspace) endValues.values[SMARTSPACE_BOUNDS] as Rect else null + val toSSBounds = endValues.values[SMARTSPACE_BOUNDS] as Rect? val toIsVis = toVis == View.VISIBLE val toAlpha = if (toIsVis) 1f else 0f @@ -141,11 +153,12 @@ class ClockSizeTransition( fun assignAnimValues(src: String, fract: Float, vis: Int? = null) { val bounds = computeBounds(fract) val alpha = MathUtils.lerp(fromAlpha, toAlpha, fract) - if (DEBUG) + if (DEBUG) { Log.i( TAG, "$src: $toView; fract=$fract; alpha=$alpha; vis=$vis; bounds=$bounds;" ) + } toView.setVisibility(vis ?: View.VISIBLE) toView.setAlpha(alpha) toView.setRect(bounds) @@ -206,9 +219,6 @@ class ClockSizeTransition( private const val SMARTSPACE_BOUNDS = "ClockSizeTransition:SSBounds" private val TRANSITION_PROPERTIES = arrayOf(PROP_VISIBILITY, PROP_ALPHA, PROP_BOUNDS, SMARTSPACE_BOUNDS) - - private val DEBUG = false - private val TAG = VisibilityBoundsTransition::class.simpleName!! } } @@ -217,18 +227,24 @@ class ClockSizeTransition( val viewModel: KeyguardClockViewModel, val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : VisibilityBoundsTransition() { + override val captureSmartspace = !viewModel.isLargeClockVisible.value + init { duration = CLOCK_IN_MILLIS startDelay = CLOCK_IN_START_DELAY_MILLIS interpolator = CLOCK_IN_INTERPOLATOR - captureSmartspace = - !viewModel.isLargeClockVisible.value && smartspaceViewModel.isSmartspaceEnabled if (viewModel.isLargeClockVisible.value) { viewModel.currentClock.value?.let { + if (DEBUG) Log.i(TAG, "Large Clock In: ${it.largeClock.layout.views}") it.largeClock.layout.views.forEach { addTarget(it) } } + ?: run { + Log.e(TAG, "No large clock set, falling back") + addTarget(R.id.lockscreen_clock_view_large) + } } else { + if (DEBUG) Log.i(TAG, "Small Clock In") addTarget(R.id.lockscreen_clock_view) } } @@ -245,12 +261,9 @@ class ClockSizeTransition( // Move normally if clock is not changing visibility if (fromIsVis == toIsVis) return - fromBounds.left = toBounds.left - fromBounds.right = toBounds.right + fromBounds.set(toBounds) if (viewModel.isLargeClockVisible.value) { - // Large clock shouldn't move - fromBounds.top = toBounds.top - fromBounds.bottom = toBounds.bottom + // Large clock shouldn't move; fromBounds already set } else if (toSSBounds != null && fromSSBounds != null) { // Instead of moving the small clock the full distance, we compute the distance // smartspace will move. We then scale this to match the duration of this animation @@ -270,7 +283,6 @@ class ClockSizeTransition( val CLOCK_IN_INTERPOLATOR = Interpolators.LINEAR_OUT_SLOW_IN const val SMALL_CLOCK_IN_MOVE_SCALE = CLOCK_IN_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_DOWN_MILLIS.toFloat() - private val TAG = ClockFaceInTransition::class.simpleName!! } } @@ -279,18 +291,24 @@ class ClockSizeTransition( val viewModel: KeyguardClockViewModel, val smartspaceViewModel: KeyguardSmartspaceViewModel, ) : VisibilityBoundsTransition() { + override val captureSmartspace = viewModel.isLargeClockVisible.value + init { duration = CLOCK_OUT_MILLIS interpolator = CLOCK_OUT_INTERPOLATOR - captureSmartspace = - viewModel.isLargeClockVisible.value && smartspaceViewModel.isSmartspaceEnabled if (viewModel.isLargeClockVisible.value) { + if (DEBUG) Log.i(TAG, "Small Clock Out") addTarget(R.id.lockscreen_clock_view) } else { viewModel.currentClock.value?.let { + if (DEBUG) Log.i(TAG, "Large Clock Out: ${it.largeClock.layout.views}") it.largeClock.layout.views.forEach { addTarget(it) } } + ?: run { + Log.e(TAG, "No large clock set, falling back") + addTarget(R.id.lockscreen_clock_view_large) + } } } @@ -306,12 +324,9 @@ class ClockSizeTransition( // Move normally if clock is not changing visibility if (fromIsVis == toIsVis) return - toBounds.left = fromBounds.left - toBounds.right = fromBounds.right + toBounds.set(fromBounds) if (!viewModel.isLargeClockVisible.value) { - // Large clock shouldn't move - toBounds.top = fromBounds.top - toBounds.bottom = fromBounds.bottom + // Large clock shouldn't move; toBounds already set } else if (toSSBounds != null && fromSSBounds != null) { // Instead of moving the small clock the full distance, we compute the distance // smartspace will move. We then scale this to match the duration of this animation @@ -321,7 +336,7 @@ class ClockSizeTransition( toBounds.top = fromBounds.top - ssTranslation toBounds.bottom = fromBounds.bottom - ssTranslation } else { - Log.w(TAG, "mutateBounds: smallClock received no smartspace bounds") + Log.e(TAG, "mutateBounds: smallClock received no smartspace bounds") } } @@ -330,7 +345,6 @@ class ClockSizeTransition( val CLOCK_OUT_INTERPOLATOR = Interpolators.LINEAR const val SMALL_CLOCK_OUT_MOVE_SCALE = CLOCK_OUT_MILLIS / SmartspaceMoveTransition.STATUS_AREA_MOVE_UP_MILLIS.toFloat() - private val TAG = ClockFaceOutTransition::class.simpleName!! } } @@ -339,6 +353,8 @@ class ClockSizeTransition( val config: IntraBlueprintTransition.Config, viewModel: KeyguardClockViewModel, ) : VisibilityBoundsTransition() { + override val captureSmartspace = false + init { duration = if (viewModel.isLargeClockVisible.value) STATUS_AREA_MOVE_UP_MILLIS @@ -358,4 +374,8 @@ class ClockSizeTransition( const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L } } + + companion object { + val DEBUG = true + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt index 4fd92d70fb07..9da11ceb8c1f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -42,8 +44,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromAlternateBouncerTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.AOD, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = AOD), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt index 9649af73eadb..55a48b6bd49b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToDozingTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -42,8 +44,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromAlternateBouncerTransitionInteractor.TO_DOZING_DURATION, - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.DOZING, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = DOZING), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt index 8c6be989d8d9..bb4fb7961ed0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToGoneTransitionViewModel.kt @@ -19,11 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_GONE_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.SysuiStatusBarStateController import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -44,11 +46,14 @@ constructor( private val statusBarStateController: SysuiStatusBarStateController, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_GONE_DURATION, - from = ALTERNATE_BOUNCER, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = TO_GONE_DURATION, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = ALTERNATE_BOUNCER, to = GONE), + ) fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { var startAlpha = 1f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt index 27febd38a97c..3f2ef29c9570 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToOccludedTransitionViewModel.kt @@ -18,8 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor.Companion.TO_OCCLUDED_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -40,8 +41,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_OCCLUDED_DURATION, - from = ALTERNATE_BOUNCER, - to = KeyguardState.OCCLUDED, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = OCCLUDED), ) override val deviceEntryParentViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt index 759288136783..f0bccacadc57 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToPrimaryBouncerTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAlternateBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -37,11 +40,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, - from = KeyguardState.ALTERNATE_BOUNCER, - to = KeyguardState.PRIMARY_BOUNCER, - ) + animationFlow + .setup( + duration = FromAlternateBouncerTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + edge = Edge.create(from = ALTERNATE_BOUNCER, to = Scenes.Bouncer), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = ALTERNATE_BOUNCER, to = PRIMARY_BOUNCER), + ) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt index 5cf100e78e6e..4128c529644d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt @@ -34,8 +34,8 @@ constructor( alternateBouncerInteractor: AlternateBouncerInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { - private val deviceSupportsAlternateBouncer: Flow<Boolean> = - alternateBouncerInteractor.alternateBouncerSupported + val canShowAlternateBouncer: Flow<Boolean> = alternateBouncerInteractor.canShowAlternateBouncer + private val isTransitioningToOrFromOrShowingAlternateBouncer: Flow<Boolean> = keyguardTransitionInteractor .transitionValue(KeyguardState.ALTERNATE_BOUNCER) @@ -43,8 +43,8 @@ constructor( .distinctUntilChanged() val alternateBouncerWindowRequired: Flow<Boolean> = - deviceSupportsAlternateBouncer.flatMapLatest { deviceSupportsAlternateBouncer -> - if (deviceSupportsAlternateBouncer) { + canShowAlternateBouncer.flatMapLatest { canShowAlternateBouncer -> + if (canShowAlternateBouncer) { isTransitioningToOrFromOrShowingAlternateBouncer } else { flowOf(false) 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/AodToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt index adc090de1d4a..8e8b09d5cd67 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToGoneTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -37,11 +40,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromAodTransitionInteractor.TO_GONE_DURATION, - from = KeyguardState.AOD, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = FromAodTransitionInteractor.TO_GONE_DURATION, + edge = Edge.create(from = AOD, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = AOD, to = GONE), + ) /** * AOD -> GONE should fade out the lockscreen contents. This transition plays both during wake diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt index cbbb82039329..b267ecb42ac2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModel.kt @@ -19,9 +19,10 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition @@ -39,7 +40,6 @@ import kotlinx.coroutines.flow.Flow class AodToLockscreenTransitionViewModel @Inject constructor( - deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor, shadeInteractor: ShadeInteractor, animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { @@ -47,8 +47,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = AOD, to = LOCKSCREEN), ) private var isShadeExpanded = false diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt index 445575f7e55d..2497defba5a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToOccludedTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -36,8 +38,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION, - from = KeyguardState.AOD, - to = KeyguardState.OCCLUDED, + edge = Edge.create(from = AOD, to = OCCLUDED), ) /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt index 9a23007eea4a..35f05f55caa1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodToPrimaryBouncerTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -37,11 +40,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, - from = KeyguardState.AOD, - to = KeyguardState.PRIMARY_BOUNCER, - ) + animationFlow + .setup( + duration = FromAodTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + edge = Edge.create(from = AOD, to = Scenes.Bouncer), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = AOD, to = PRIMARY_BOUNCER), + ) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt index e0a3af6021c0..caa043622e18 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlows.kt @@ -19,11 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.SysuiStatusBarStateController import dagger.Lazy @@ -73,8 +75,12 @@ constructor( return animationFlow .setup( duration = duration, - from = from, - to = GONE, + // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene -> scene + // transition + edge = Edge.create(from = from, to = Scenes.Gone) + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = from, to = GONE), ) .sharedFlow( duration = duration, @@ -96,43 +102,51 @@ constructor( var leaveShadeOpen: Boolean = false var willRunDismissFromKeyguard: Boolean = false val transitionAnimation = - animationFlow.setup( - duration = duration, - from = fromState, - to = GONE, - ) - - return shadeInteractor.isAnyExpanded.flatMapLatest { isAnyExpanded -> - transitionAnimation - .sharedFlow( + animationFlow + .setup( duration = duration, - interpolator = EMPHASIZED_ACCELERATE, - onStart = { - leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() - willRunDismissFromKeyguard = willRunAnimationOnKeyguard() - isShadeExpanded = isAnyExpanded - }, - onStep = { 1f - it }, + // TODO(b/330311871): from can be PRIMARY_BOUNCER which would be a scene -> + // scene transition + edge = Edge.create(from = fromState, to = Scenes.Gone), ) - .map { - if (willRunDismissFromKeyguard) { - if (isShadeExpanded) { + .setupWithoutSceneContainer( + edge = Edge.create(from = fromState, to = GONE), + ) + + return shadeInteractor.anyExpansion + .map { it > 0f } + .distinctUntilChanged() + .flatMapLatest { isAnyExpanded -> + transitionAnimation + .sharedFlow( + duration = duration, + interpolator = EMPHASIZED_ACCELERATE, + onStart = { + leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() + willRunDismissFromKeyguard = willRunAnimationOnKeyguard() + isShadeExpanded = isAnyExpanded + }, + onStep = { 1f - it }, + ) + .map { + if (willRunDismissFromKeyguard) { + if (isShadeExpanded) { + ScrimAlpha( + behindAlpha = it, + notificationsAlpha = it, + ) + } else { + ScrimAlpha() + } + } else if (leaveShadeOpen) { ScrimAlpha( - behindAlpha = it, - notificationsAlpha = it, + behindAlpha = 1f, + notificationsAlpha = 1f, ) } else { - ScrimAlpha() + ScrimAlpha(behindAlpha = it) } - } else if (leaveShadeOpen) { - ScrimAlpha( - behindAlpha = 1f, - notificationsAlpha = 1f, - ) - } else { - ScrimAlpha(behindAlpha = it) } - } - } + } } } 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 e26b75fa9d5f..fa43ec2cbc06 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,8 @@ 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.biometrics.shared.model.SensorLocation import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor @@ -68,9 +70,16 @@ 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 + val udfpsLocation: StateFlow<SensorLocation?> = + deviceEntryUdfpsInteractor.udfpsLocation.stateIn( + scope = scope, + started = SharingStarted.Eagerly, + initialValue = null, + ) private val intEvaluator = IntEvaluator() private val floatEvaluator = FloatEvaluator() private val showingAlternateBouncer: Flow<Boolean> = @@ -84,19 +93,21 @@ constructor( .map { it.deviceEntryParentViewAlpha } .merge() .shareIn(scope, SharingStarted.WhileSubscribed()) + .onStart { emit(initialAlphaFromKeyguardState(transitionInteractor.getCurrentState())) } private val alphaMultiplierFromShadeExpansion: Flow<Float> = combine( - showingAlternateBouncer, - shadeExpansion, - qsProgress, - ) { showingAltBouncer, shadeExpansion, qsProgress -> - val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f) - if (showingAltBouncer) { - 1f - } else { - (1f - shadeExpansion) * (1f - interpolatedQsProgress) + showingAlternateBouncer, + shadeExpansion, + qsProgress, + ) { showingAltBouncer, shadeExpansion, qsProgress -> + val interpolatedQsProgress = (qsProgress * 2).coerceIn(0f, 1f) + if (showingAltBouncer) { + 1f + } else { + (1f - shadeExpansion) * (1f - interpolatedQsProgress) + } } - } + .onStart { emit(1f) } // Burn-in offsets in AOD private val nonAnimatedBurnInOffsets: Flow<BurnInOffsets> = combine( @@ -122,14 +133,35 @@ constructor( ) } - val deviceEntryViewAlpha: StateFlow<Float> = + val deviceEntryViewAlpha: Flow<Float> = combine( transitionAlpha, alphaMultiplierFromShadeExpansion, ) { alpha, alphaMultiplier -> alpha * alphaMultiplier } - .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(), initialValue = 0f) + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(), + initialValue = 0f, + ) + + private fun initialAlphaFromKeyguardState(keyguardState: KeyguardState): Float { + return when (keyguardState) { + KeyguardState.OFF, + KeyguardState.PRIMARY_BOUNCER, + KeyguardState.DOZING, + KeyguardState.DREAMING, + KeyguardState.GLANCEABLE_HUB, + KeyguardState.GONE, + KeyguardState.OCCLUDED, + KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + KeyguardState.UNDEFINED, -> 0f + KeyguardState.AOD, + KeyguardState.ALTERNATE_BOUNCER, + KeyguardState.LOCKSCREEN, -> 1f + } + } val useBackgroundProtection: StateFlow<Boolean> = isUdfpsSupported val burnInOffsets: Flow<BurnInOffsets> = deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled @@ -175,19 +207,24 @@ constructor( .distinctUntilChanged() private val isUnlocked: Flow<Boolean> = - keyguardInteractor.isKeyguardDismissible.flatMapLatest { isUnlocked -> - if (!isUnlocked) { - flowOf(false) + if (SceneContainerFlag.isEnabled) { + deviceEntryInteractor.isUnlocked } else { - flow { - // delay in case device ends up transitioning away from the lock screen; - // we don't want to animate to the unlocked icon and just let the - // icon fade with the transition to GONE - delay(UNLOCKED_DELAY_MS) - emit(true) + keyguardInteractor.isKeyguardDismissible + } + .flatMapLatest { isUnlocked -> + if (!isUnlocked) { + flowOf(false) + } else { + flow { + // delay in case device ends up transitioning away from the lock screen; + // we don't want to animate to the unlocked icon and just let the + // icon fade with the transition to GONE + delay(UNLOCKED_DELAY_MS) + emit(true) + } } } - } val iconType: Flow<DeviceEntryIconView.IconType> = combine( @@ -195,7 +232,14 @@ constructor( isUnlocked, ) { isListeningForUdfps, isUnlocked -> if (isListeningForUdfps) { - DeviceEntryIconView.IconType.FINGERPRINT + if (isUnlocked) { + // Don't show any UI until isUnlocked=false. This covers the case + // when the "Power button instantly locks > 0s" or the device doesn't lock + // immediately after a screen time. + DeviceEntryIconView.IconType.NONE + } else { + DeviceEntryIconView.IconType.FINGERPRINT + } } else if (isUnlocked) { DeviceEntryIconView.IconType.UNLOCK } else { @@ -203,7 +247,8 @@ constructor( } } val isVisible: Flow<Boolean> = deviceEntryViewAlpha.map { it > 0f }.distinctUntilChanged() - val isLongPressEnabled: Flow<Boolean> = + + private val isInteractive: Flow<Boolean> = combine( iconType, isUdfpsSupported, @@ -211,20 +256,28 @@ constructor( when (deviceEntryStatus) { DeviceEntryIconView.IconType.LOCK -> isUdfps DeviceEntryIconView.IconType.UNLOCK -> true - DeviceEntryIconView.IconType.FINGERPRINT -> false + DeviceEntryIconView.IconType.FINGERPRINT, + 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 { @@ -239,8 +292,8 @@ constructor( DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.AUTHENTICATE DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER - DeviceEntryIconView.IconType.FINGERPRINT -> - DeviceEntryIconView.AccessibilityHintType.NONE + DeviceEntryIconView.IconType.FINGERPRINT, + DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt index 8851a51f15b0..77ebfcea9c96 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGoneTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_GONE_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -37,11 +40,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_GONE_DURATION, - from = KeyguardState.DOZING, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = TO_GONE_DURATION, + edge = Edge.create(from = DOZING, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = DOZING, to = GONE), + ) fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> { var startAlpha = 1f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt index 168d6e16daa2..a460d515e0b2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToLockscreenTransitionViewModel.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -39,8 +41,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromDozingTransitionInteractor.TO_LOCKSCREEN_DURATION, - from = KeyguardState.DOZING, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = DOZING, to = LOCKSCREEN), ) val shortcutsAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt index c0b11959cbd9..f33752fc04d4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToOccludedTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromAodTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -38,8 +40,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromAodTransitionInteractor.TO_OCCLUDED_DURATION, - from = KeyguardState.DOZING, - to = KeyguardState.OCCLUDED, + edge = Edge.create(from = DOZING, to = OCCLUDED), ) /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt index 4395c3436a71..7ddf641e9e8e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToPrimaryBouncerTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDozingTransitionInteractor.Companion.TO_PRIMARY_BOUNCER_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -38,11 +41,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_PRIMARY_BOUNCER_DURATION, - from = KeyguardState.DOZING, - to = KeyguardState.PRIMARY_BOUNCER, - ) + animationFlow + .setup( + duration = TO_PRIMARY_BOUNCER_DURATION, + edge = Edge.create(from = DOZING, to = Scenes.Bouncer), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = DOZING, to = PRIMARY_BOUNCER), + ) override val deviceEntryParentViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt index 67568e12a4a1..57ed455e7b13 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingHostedToLockscreenTransitionViewModel.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDreamingLockscreenHostedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -34,8 +36,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = DREAMING_LOCKSCREEN_HOSTED, to = LOCKSCREEN), ) val shortcutsAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt index 0fa74752ea0d..754ed6cc3327 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToAodTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -40,12 +42,12 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromDreamingTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.DREAMING, - to = KeyguardState.AOD, + edge = Edge.create(from = DREAMING, to = AOD), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) + override val deviceEntryParentViewAlpha: Flow<Float> = deviceEntryUdfpsInteractor.isUdfpsEnrolledAndEnabled.flatMapLatest { udfpsEnrolledAndEnabled -> 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..00aa102ec5bb 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 @@ -19,9 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED 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.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -37,13 +41,16 @@ class DreamingToGlanceableHubTransitionViewModel constructor( animationFlow: KeyguardTransitionAnimationFlow, configurationInteractor: ConfigurationInteractor, -) { +) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_GLANCEABLE_HUB_DURATION, - from = KeyguardState.DREAMING, - to = KeyguardState.GLANCEABLE_HUB, - ) + animationFlow + .setup( + duration = TO_GLANCEABLE_HUB_DURATION, + edge = Edge.create(from = DREAMING, to = Scenes.Communal), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = DREAMING, to = GLANCEABLE_HUB), + ) val dreamOverlayTranslationX: Flow<Float> = configurationInteractor @@ -79,6 +86,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/DreamingToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt index ec7b931161f6..1bdf6d29f6af 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGoneTransitionViewModel.kt @@ -18,10 +18,13 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow -import kotlinx.coroutines.flow.Flow +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject +import kotlinx.coroutines.flow.Flow @SysUISingleton class DreamingToGoneTransitionViewModel @@ -31,13 +34,15 @@ constructor( ) { private val transitionAnimation = - animationFlow.setup( + animationFlow + .setup( duration = FromDreamingTransitionInteractor.TO_GONE_DURATION, - from = KeyguardState.DREAMING, - to = KeyguardState.GONE, + edge = Edge.create(from = DREAMING, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = DREAMING, to = GONE), ) /** Lockscreen views alpha */ val lockscreenAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) - -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index f191aa7d7e4e..82381eb45f9a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -20,7 +20,9 @@ import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -45,8 +47,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.DREAMING, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = DREAMING, to = LOCKSCREEN), ) /** Dream overlay y-translation on exit */ 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..d594488208a1 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 @@ -19,9 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators 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.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -37,14 +41,17 @@ class GlanceableHubToDreamingTransitionViewModel constructor( animationFlow: KeyguardTransitionAnimationFlow, configurationInteractor: ConfigurationInteractor, -) { +) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FROM_GLANCEABLE_HUB_DURATION, - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.DREAMING, - ) + animationFlow + .setup( + duration = FROM_GLANCEABLE_HUB_DURATION, + edge = Edge.create(from = Scenes.Communal, to = DREAMING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GLANCEABLE_HUB, to = DREAMING), + ) val dreamOverlayAlpha: Flow<Float> = transitionAnimation.sharedFlow( @@ -78,6 +85,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/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt index e05b500620d5..046b95f0c6ae 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt @@ -20,10 +20,13 @@ import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGlanceableHubTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,11 +48,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = - animationFlow.setup( - duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.GLANCEABLE_HUB, - to = KeyguardState.LOCKSCREEN, - ) + animationFlow + .setup( + duration = TO_LOCKSCREEN_DURATION, + edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN), + ) val keyguardAlpha: Flow<Float> = transitionAnimation.sharedFlow( 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..cd98bb00b9dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToOccludedTransitionViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.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.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes +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, + edge = Edge.create(from = Scenes.Communal, to = OCCLUDED), + ) + .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = OCCLUDED)) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(0f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt index 3540bec5d3e7..74f7d75fa326 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModel.kt @@ -20,10 +20,13 @@ import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_AOD_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,11 +45,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_AOD_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.AOD, - ) + animationFlow + .setup( + duration = TO_AOD_DURATION, + edge = Edge.create(from = Scenes.Gone, to = AOD), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = AOD), + ) /** y-translation from the top of the screen for AOD */ fun enterFromTopTranslationY(translatePx: Int): Flow<StateToValue> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt index 80a6bda65b99..70c0032a30b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDozingTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DOZING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -40,11 +43,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_DOZING_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.DOZING, - ) + animationFlow + .setup( + duration = TO_DOZING_DURATION, + edge = Edge.create(from = Scenes.Gone, to = DOZING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = DOZING), + ) val lockscreenAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt index b52746364a8b..627f0de696d7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingLockscreenHostedTransitionViewModel.kt @@ -18,8 +18,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -36,11 +39,14 @@ constructor( ) { private val transitionAnimation = - animationFlow.setup( - duration = TO_DREAMING_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, - ) + animationFlow + .setup( + duration = TO_DREAMING_DURATION, + edge = Edge.create(from = Scenes.Gone, to = DREAMING_LOCKSCREEN_HOSTED), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = DREAMING_LOCKSCREEN_HOSTED), + ) /** Lockscreen views alpha - hide immediately */ val lockscreenAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt index 102242a4a7b0..f8b6e2819b9b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModel.kt @@ -19,8 +19,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_DREAMING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -34,11 +37,14 @@ constructor( ) { private val transitionAnimation = - animationFlow.setup( - duration = TO_DREAMING_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.DREAMING, - ) + animationFlow + .setup( + duration = TO_DREAMING_DURATION, + edge = Edge.create(from = Scenes.Gone, to = DREAMING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = DREAMING), + ) /** Lockscreen views y-translation */ fun lockscreenTranslationY(translatePx: Int): Flow<Float> { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt index a2ce408955a1..08ec43f9ae5f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GoneToLockscreenTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromGoneTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -33,11 +36,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN - ) + animationFlow + .setup( + duration = TO_LOCKSCREEN_DURATION, + edge = Edge.create(from = Scenes.Gone, to = LOCKSCREEN), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = GONE, to = LOCKSCREEN), + ) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( 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/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 24a7c512a6a9..f8a9310e091f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -30,6 +30,7 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.BurnInModel +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -38,11 +39,12 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.ui.StateToValue +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.statusbar.phone.ScreenOffAnimationController -import com.android.systemui.util.kotlin.BooleanFlowOperators.or +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample import com.android.systemui.util.ui.AnimatableEvent @@ -56,6 +58,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged @@ -64,7 +67,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.launch @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton @@ -116,14 +118,19 @@ constructor( private val shadeInteractor: ShadeInteractor, ) { private var burnInJob: Job? = null - private val burnInModel = MutableStateFlow(BurnInModel()) + private val _burnInModel = MutableStateFlow(BurnInModel()) + val burnInModel = _burnInModel.asStateFlow() val burnInLayerVisibility: Flow<Int> = keyguardTransitionInteractor.startedKeyguardState .filter { it == AOD || it == LOCKSCREEN } .map { VISIBLE } - val goneToAodTransition = keyguardTransitionInteractor.transition(from = GONE, to = AOD) + val goneToAodTransition = + keyguardTransitionInteractor.transition( + edge = Edge.create(Scenes.Gone, AOD), + edgeWithoutSceneContainer = Edge.create(GONE, AOD) + ) private val goneToAodTransitionRunning: Flow<Boolean> = goneToAodTransition @@ -134,7 +141,7 @@ constructor( private val isOnLockscreen: Flow<Boolean> = combine( keyguardTransitionInteractor.isFinishedInState(LOCKSCREEN).onStart { emit(false) }, - or( + anyOf( keyguardTransitionInteractor.isInTransitionToState(LOCKSCREEN), keyguardTransitionInteractor.isInTransitionFromState(LOCKSCREEN), ), @@ -145,7 +152,10 @@ constructor( private val alphaOnShadeExpansion: Flow<Float> = combineTransform( - keyguardTransitionInteractor.isInTransition(from = LOCKSCREEN, to = GONE), + keyguardTransitionInteractor.isInTransition( + edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone), + edgeWithoutSceneContainer = Edge.create(from = LOCKSCREEN, to = GONE), + ), isOnLockscreen, shadeInteractor.qsExpansion, shadeInteractor.shadeExpansion, @@ -271,7 +281,7 @@ constructor( burnInJob = scope.launch("$TAG#aodBurnInViewModel") { - aodBurnInViewModel.movement(params).collect { burnInModel.value = it } + aodBurnInViewModel.movement(params).collect { _burnInModel.value = it } } } 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 d4c8456e0d71..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 @@ -88,12 +89,16 @@ constructor( isCommunalAvailable: Boolean, shadeMode: ShadeMode, ): Map<UserAction, UserActionResult> { + val shadeSceneKey = + 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 { - Scenes.Shade - } + if (shadeMode is ShadeMode.Single) UserActionResult(Scenes.QuickSettings) + else shadeSceneKey return mapOf( Swipe.Left to UserActionResult(Scenes.Communal).takeIf { isCommunalAvailable }, @@ -101,11 +106,17 @@ constructor( // Swiping down from the top edge goes to QS (or shade if in split shade mode). swipeDownFromTop(pointerCount = 1) to quickSettingsIfSingleShade, - swipeDownFromTop(pointerCount = 2) to quickSettingsIfSingleShade, + swipeDownFromTop(pointerCount = 2) to + // TODO(b/338577208): Remove 'Dual' once we add Dual Shade invocation zones. + if (shadeMode is ShadeMode.Dual) { + UserActionResult(Scenes.QuickSettingsShade) + } else { + quickSettingsIfSingleShade + }, // Swiping down, not from the edge, always navigates to the shade scene. - swipeDown(pointerCount = 1) to Scenes.Shade, - swipeDown(pointerCount = 2) to Scenes.Shade, + swipeDown(pointerCount = 1) to shadeSceneKey, + swipeDown(pointerCount = 2) to shadeSceneKey, ) .filterValues { it != null } .mapValues { checkNotNull(it.value) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt index 1f9f3043dfdf..8b5b347a763d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModel.kt @@ -20,7 +20,9 @@ import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -45,8 +47,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromLockscreenTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, + edge = Edge.create(from = LOCKSCREEN, to = AOD), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt index c836f01e2ee9..27a1f7afb4e1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDozingTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DOZING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -40,8 +42,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_DOZING_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DOZING, + edge = Edge.create(from = LOCKSCREEN, to = DOZING), ) val lockscreenAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt index 19b9cf4733f9..778dbed90ec1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingHostedTransitionViewModel.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_HOSTED_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -34,8 +36,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_DREAMING_HOSTED_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DREAMING_LOCKSCREEN_HOSTED, + edge = Edge.create(from = LOCKSCREEN, to = DREAMING_LOCKSCREEN_HOSTED), ) val shortcutsAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt index 13522a6742ac..579abeb7e092 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_DREAMING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -40,8 +42,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_DREAMING_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DREAMING, + edge = Edge.create(from = LOCKSCREEN, to = DREAMING), ) /** Lockscreen views y-translation */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt index dae7897a2325..c7273b7cfd48 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt @@ -20,10 +20,13 @@ import com.android.app.animation.Interpolators.EMPHASIZED import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.StateToValue import com.android.systemui.res.R +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,11 +48,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = - animationFlow.setup( - duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GLANCEABLE_HUB, - ) + animationFlow + .setup( + duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION, + edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB), + ) val keyguardAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt index f03625eda9b5..1314e8863c71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGoneTransitionViewModel.kt @@ -19,10 +19,13 @@ package com.android.systemui.keyguard.ui.viewmodel import android.util.MathUtils import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow.FlowBuilder import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.SysuiStatusBarStateController import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -42,11 +45,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation: FlowBuilder = - animationFlow.setup( - duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = FromLockscreenTransitionInteractor.TO_GONE_DURATION, + edge = Edge.create(from = LOCKSCREEN, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = LOCKSCREEN, to = GONE), + ) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt index dd6652e69792..fcf8c14fc326 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModel.kt @@ -20,7 +20,9 @@ import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor.Companion.TO_OCCLUDED_DURATION +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R @@ -45,8 +47,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_OCCLUDED_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.OCCLUDED, + edge = Edge.create(from = KeyguardState.LOCKSCREEN, to = OCCLUDED), ) /** Lockscreen views alpha */ diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt index 0cfc75757b7d..23c44b0a38fb 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt @@ -18,9 +18,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -39,11 +42,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.PRIMARY_BOUNCER, - ) + animationFlow + .setup( + duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION, + edge = Edge.create(from = LOCKSCREEN, to = Scenes.Bouncer), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = LOCKSCREEN, to = PRIMARY_BOUNCER), + ) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt index d7ba46b6e708..706a3c440723 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToAodTransitionViewModel.kt @@ -19,7 +19,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject @@ -41,8 +43,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromOccludedTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.AOD, + edge = Edge.create(from = OCCLUDED, to = AOD), ) val deviceEntryBackgroundViewAlpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt index 91554e3e914a..af019300c764 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToDozingTransitionViewModel.kt @@ -18,7 +18,9 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds @@ -38,8 +40,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = FromOccludedTransitionInteractor.TO_DOZING_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.DOZING, + edge = Edge.create(from = OCCLUDED, to = DOZING), ) /** Lockscreen views alpha */ 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..47e202b8fcc3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGlanceableHubTransitionViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.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.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED +import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes +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, + edge = Edge.create(OCCLUDED, Scenes.Communal) + ) + .setupWithoutSceneContainer(edge = Edge.create(OCCLUDED, GLANCEABLE_HUB)) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt index d2c9cfbd71b8..98dba393a545 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToGoneTransitionViewModel.kt @@ -17,8 +17,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -33,11 +36,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = - animationFlow.setup( - duration = DEFAULT_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.GONE, - ) + animationFlow + .setup( + duration = DEFAULT_DURATION, + edge = Edge.create(from = OCCLUDED, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = OCCLUDED, to = GONE), + ) fun notificationAlpha(viewState: ViewStateAccessor): Flow<Float> { var currentAlpha = 0f diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt index a09d58ac381b..36c7d5b98dba 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModel.kt @@ -23,7 +23,9 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsIntera import com.android.systemui.keyguard.domain.interactor.FromOccludedTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.res.R @@ -56,8 +58,7 @@ constructor( private val transitionAnimation = animationFlow.setup( duration = TO_LOCKSCREEN_DURATION, - from = KeyguardState.OCCLUDED, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = OCCLUDED, to = LOCKSCREEN), ) /** Lockscreen views y-translation */ @@ -101,7 +102,7 @@ constructor( .filter { (wasOccluded, isOccluded) -> wasOccluded && !isOccluded && - keyguardTransitionInteractor.getCurrentState() == KeyguardState.OCCLUDED + keyguardTransitionInteractor.getCurrentState() == OCCLUDED } .map { 0f } ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt index 74094bea140a..1eecbd5fbda1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/OffToLockscreenTransitionViewModel.kt @@ -17,8 +17,11 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.flow.Flow @@ -28,13 +31,12 @@ class OffToLockscreenTransitionViewModel @Inject constructor( animationFlow: KeyguardTransitionAnimationFlow, -) { +) : DeviceEntryIconTransition { private val transitionAnimation = animationFlow.setup( duration = 250.milliseconds, - from = KeyguardState.OFF, - to = KeyguardState.LOCKSCREEN, + edge = Edge.create(from = OFF, to = LOCKSCREEN), ) val shortcutsAlpha: Flow<Float> = @@ -43,4 +45,7 @@ constructor( onStep = { it }, onCancel = { 0f }, ) + + override val deviceEntryParentViewAlpha: Flow<Float> = + transitionAnimation.immediatelyTransitionTo(1f) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt index 942903bbabd7..009f85d4bcb9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToAodTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -42,11 +45,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.AOD, - ) + animationFlow + .setup( + duration = FromPrimaryBouncerTransitionInteractor.TO_AOD_DURATION, + edge = Edge.create(from = Scenes.Bouncer, to = AOD), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = PRIMARY_BOUNCER, to = AOD), + ) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt index 13f651a9ff5d..e5bb46432226 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToDozingTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_DOZING_DURATION -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -42,11 +45,14 @@ constructor( ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = TO_DOZING_DURATION, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.DOZING, - ) + animationFlow + .setup( + duration = TO_DOZING_DURATION, + edge = Edge.create(from = Scenes.Bouncer, to = DOZING), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = PRIMARY_BOUNCER, to = DOZING), + ) val deviceEntryBackgroundViewAlpha: Flow<Float> = transitionAnimation.immediatelyTransitionTo(0f) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt index b1fa7101804f..7ae455818952 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt @@ -20,11 +20,13 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.SysuiStatusBarStateController import dagger.Lazy import javax.inject.Inject @@ -49,11 +51,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) { private val transitionAnimation = - animationFlow.setup( - duration = TO_GONE_DURATION, - from = PRIMARY_BOUNCER, - to = GONE, - ) + animationFlow + .setup( + duration = TO_GONE_DURATION, + edge = Edge.create(from = PRIMARY_BOUNCER, to = Scenes.Gone), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = PRIMARY_BOUNCER, to = GONE), + ) private var leaveShadeOpen: Boolean = false private var willRunDismissFromKeyguard: Boolean = false @@ -88,6 +93,7 @@ constructor( } else { createBouncerAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard) } + private fun createBouncerAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> { return transitionAnimation.sharedFlow( duration = 200.milliseconds, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt index 25750415e88f..7511101bf04e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt @@ -19,9 +19,12 @@ package com.android.systemui.keyguard.ui.viewmodel import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -39,11 +42,14 @@ constructor( animationFlow: KeyguardTransitionAnimationFlow, ) : DeviceEntryIconTransition { private val transitionAnimation = - animationFlow.setup( - duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, - from = KeyguardState.PRIMARY_BOUNCER, - to = KeyguardState.LOCKSCREEN, - ) + animationFlow + .setup( + duration = FromPrimaryBouncerTransitionInteractor.TO_LOCKSCREEN_DURATION, + edge = Edge.create(from = Scenes.Bouncer, to = LOCKSCREEN), + ) + .setupWithoutSceneContainer( + edge = Edge.create(from = PRIMARY_BOUNCER, to = LOCKSCREEN), + ) val shortcutsAlpha: Flow<Float> = transitionAnimation.sharedFlow( diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index 9e6c5520d1b7..b276f532e874 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -201,6 +201,10 @@ constructor( ) } + fun addLockoutResetCallbackDone() { + logBuffer.log(TAG, DEBUG, {}, { "addlockoutResetCallback done" }) + } + fun authRequested(uiEvent: FaceAuthUiEvent) { logBuffer.log( TAG, 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..486d4d46c767 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 @@ -102,7 +102,8 @@ constructor( return } val controller = data.token?.let { controllerFactory.create(it) } - val localMediaManager = localMediaManagerFactory.create(data.packageName) + val localMediaManager = + localMediaManagerFactory.create(data.packageName, controller?.sessionToken) val muteAwaitConnectionManager = muteAwaitConnectionManagerFactory.create(localMediaManager) entry = Entry(key, oldKey, controller, localMediaManager, muteAwaitConnectionManager) @@ -111,10 +112,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 +137,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( @@ -224,9 +225,9 @@ constructor( } @WorkerThread - override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) { - val newPlaybackType = info?.playbackType ?: PLAYBACK_TYPE_UNKNOWN - val newPlaybackVolumeControlId = info?.volumeControlId + override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) { + val newPlaybackType = info.playbackType + val newPlaybackVolumeControlId = info.volumeControlId if ( newPlaybackType == playbackType && newPlaybackVolumeControlId == playbackVolumeControlId 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..3f75938a91ea 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 @@ -21,9 +21,7 @@ import android.app.BroadcastOptions import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.media.session.MediaController import android.media.session.MediaSession -import android.media.session.PlaybackState import android.provider.Settings import android.util.Log import com.android.internal.jank.Cuj @@ -42,7 +40,6 @@ import com.android.systemui.media.dialog.MediaOutputDialogManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.kotlin.pairwiseBy import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.Flow @@ -70,19 +67,6 @@ constructor( .map { entries -> entries[instanceId]?.let { toMediaControlModel(it) } } .distinctUntilChanged() - val isStartedPlaying: Flow<Boolean> = - mediaControl - .map { mediaControl -> - mediaControl?.token?.let { token -> - MediaController(applicationContext, token).playbackState?.let { - it.state == PlaybackState.STATE_PLAYING - } - } - ?: false - } - .pairwiseBy(initialValue = false) { wasPlaying, isPlaying -> !wasPlaying && isPlaying } - .distinctUntilChanged() - val onAnyMediaConfigurationChange: Flow<Unit> = repository.onAnyMediaConfigurationChange fun removeMediaControl( @@ -90,7 +74,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, @@ -170,11 +155,16 @@ constructor( return false } - fun startMediaOutputDialog(expandable: Expandable, packageName: String) { + fun startMediaOutputDialog( + expandable: Expandable, + packageName: String, + token: MediaSession.Token? = null + ) { mediaOutputDialogManager.createAndShowWithController( packageName, true, - expandable.dialogController() + expandable.dialogController(), + token = token, ) } 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/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt index 73fb5583ab3e..fed93f037638 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt @@ -260,44 +260,50 @@ object MediaControlViewBinder { } SEMANTIC_ACTIONS_ALL.forEachIndexed { index, id -> - val button = viewHolder.getAction(id) - val actionViewModel = viewModel.actionButtons[index] - if (button.id == R.id.actionPrev) { - actionViewModel?.let { - viewController.setUpPrevButtonInfo(true, it.notVisibleValue) - } - } else if (button.id == R.id.actionNext) { - actionViewModel?.let { - viewController.setUpNextButtonInfo(true, it.notVisibleValue) - } + val buttonView = viewHolder.getAction(id) + val buttonModel = viewModel.actionButtons[index] + if (buttonView.id == R.id.actionPrev) { + viewController.setUpPrevButtonInfo( + buttonModel.isEnabled, + buttonModel.notVisibleValue + ) + } else if (buttonView.id == R.id.actionNext) { + viewController.setUpNextButtonInfo( + buttonModel.isEnabled, + buttonModel.notVisibleValue + ) } - actionViewModel?.let { action -> - val animHandler = (button.tag ?: AnimationBindHandler()) as AnimationBindHandler - animHandler.tryExecute { - if (animHandler.updateRebindId(action.rebindId)) { + val animHandler = (buttonView.tag ?: AnimationBindHandler()) as AnimationBindHandler + animHandler.tryExecute { + if (buttonModel.isEnabled) { + if (animHandler.updateRebindId(buttonModel.rebindId)) { animHandler.unregisterAll() - animHandler.tryRegister(action.icon) - animHandler.tryRegister(action.background) + animHandler.tryRegister(buttonModel.icon) + animHandler.tryRegister(buttonModel.background) bindButtonCommon( - button, + buttonView, viewHolder.multiRippleView, - action, + buttonModel, viewController, falsingManager, ) } - val visible = action.isVisibleWhenScrubbing || !viewController.isScrubbing - setSemanticButtonVisibleAndAlpha( - viewHolder.getAction(id), - viewController.expandedLayout, - viewController.collapsedLayout, - visible, - action.notVisibleValue, - action.showInCollapsed - ) + } else { + animHandler.unregisterAll() + clearButton(buttonView) } + val visible = + buttonModel.isEnabled && + (buttonModel.isVisibleWhenScrubbing || !viewController.isScrubbing) + setSemanticButtonVisibleAndAlpha( + viewHolder.getAction(id), + viewController.expandedLayout, + viewController.collapsedLayout, + visible, + buttonModel.notVisibleValue, + buttonModel.showInCollapsed + ) } - ?: clearButton(button) } } else { // Hide buttons that only appear for semantic actions @@ -309,22 +315,16 @@ object MediaControlViewBinder { // Set all generic buttons genericButtons.forEachIndexed { index, button -> if (index < viewModel.actionButtons.size) { - viewModel.actionButtons[index]?.let { action -> - bindButtonCommon( - button, - viewHolder.multiRippleView, - action, - viewController, - falsingManager, - ) - setVisibleAndAlpha(expandedSet, button.id, visible = true) - setVisibleAndAlpha( - collapsedSet, - button.id, - visible = action.showInCollapsed - ) - } - ?: clearButton(button) + val action = viewModel.actionButtons[index] + bindButtonCommon( + button, + viewHolder.multiRippleView, + action, + viewController, + falsingManager, + ) + setVisibleAndAlpha(expandedSet, button.id, visible = true) + setVisibleAndAlpha(collapsedSet, button.id, visible = action.showInCollapsed) } else { // Hide any unused buttons clearButton(button) 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..19e3e0715989 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 @@ -46,6 +46,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN @@ -73,6 +74,9 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PageIndicator import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.shared.system.SysUiStatsLog.SMARTSPACE_CARD_REPORTED import com.android.systemui.shared.system.SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD @@ -142,6 +146,7 @@ constructor( private val secureSettings: SecureSettings, private val mediaCarouselViewModel: MediaCarouselViewModel, private val mediaViewControllerFactory: Provider<MediaViewController>, + private val sceneInteractor: SceneInteractor, ) : Dumpable { /** The current width of the carousel */ var currentCarouselWidth: Int = 0 @@ -190,9 +195,11 @@ constructor( @VisibleForTesting lateinit var settingsButton: View private set + 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 @@ -301,7 +308,11 @@ constructor( * It will be called when the container is out of view. */ lateinit var updateUserVisibility: () -> Unit - lateinit var updateHostVisibility: () -> Unit + var updateHostVisibility: () -> Unit = {} + set(value) { + field = value + mediaCarouselViewModel.updateHostVisibility = value + } private val isReorderingAllowed: Boolean get() = visualStabilityProvider.isReorderingAllowed @@ -338,6 +349,20 @@ constructor( configurationController.addCallback(configListener) if (!mediaFlags.isMediaControlsRefactorEnabled()) { setUpListeners() + } else { + val visualStabilityCallback = OnReorderingAllowedListener { + mediaCarouselViewModel.onReorderingAllowed() + + // Update user visibility so that no extra impression will be logged when + // activeMediaIndex resets to 0 + if (this::updateUserVisibility.isInitialized) { + updateUserVisibility() + } + + // Let's reset our scroll position + mediaCarouselScrollHandler.scrollToStart() + } + visualStabilityProvider.addPersistentReorderingAllowedListener(visualStabilityCallback) } mediaFrame.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> // The pageIndicator is not laid out yet when we get the current state update, @@ -383,12 +408,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 +500,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 +591,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) { @@ -577,9 +606,7 @@ constructor( if (!immediately) { // Although it wasn't requested, we were able to process the removal // immediately since reordering is allowed. So, notify hosts to update - if (this@MediaCarouselController::updateHostVisibility.isInitialized) { - updateHostVisibility() - } + updateHostVisibility() } } else { keysNeedRemoval.add(key) @@ -630,9 +657,13 @@ constructor( @VisibleForTesting internal fun listenForAnyStateToGoneKeyguardTransition(scope: CoroutineScope): Job { return scope.launch { - keyguardTransitionInteractor - .transition(to = GONE) - .filter { it.transitionState == TransitionState.FINISHED } + if (SceneContainerFlag.isEnabled) { + sceneInteractor.transitionState.filter { it.isIdle(Scenes.Gone) } + } else { + keyguardTransitionInteractor.transition(Edge.create(to = GONE)).filter { + it.transitionState == TransitionState.FINISHED + } + } .collect { showMediaCarousel() updateHostVisibility() @@ -644,7 +675,7 @@ constructor( internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job { return scope.launch { keyguardTransitionInteractor - .transition(to = LOCKSCREEN) + .transition(Edge.create(to = LOCKSCREEN)) .filter { it.transitionState == TransitionState.FINISHED } .collect { if (!allowMediaPlayerOnLockScreen) { @@ -732,12 +763,20 @@ constructor( } } viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded) + controllerByViewModel[commonViewModel] = viewController 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) - controllerByViewModel[commonViewModel] = viewController } private fun onUpdated(commonViewModel: MediaCommonViewModel) { @@ -1023,7 +1062,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 +1082,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 +1542,8 @@ constructor( } } - private fun onSwipeToDismiss() { + @VisibleForTesting + fun onSwipeToDismiss() { if (mediaFlags.isMediaControlsRefactorEnabled()) { mediaCarouselViewModel.onSwipeToDismiss() return @@ -1521,6 +1562,7 @@ constructor( it.mIsImpressed = false } } + MediaPlayerData.isSwipedAway = true logger.logSwipeDismiss() mediaManager.onSwipeToDismiss() } @@ -1547,6 +1589,7 @@ constructor( "state: ${desiredHostState?.expansion}, " + "only active ${desiredHostState?.showsOnlyActiveMedia}" ) + println("isSwipedAway: ${MediaPlayerData.isSwipedAway}") } } } @@ -1577,6 +1620,7 @@ internal object MediaPlayerData { // Whether should prioritize Smartspace card. internal var shouldPrioritizeSs: Boolean = false private set + internal var smartspaceMediaData: SmartspaceMediaData? = null private set @@ -1585,7 +1629,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 +1654,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..5ec4f88721ca 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 @@ -743,7 +743,8 @@ public class MediaControlPanel { mPackageName, /* aboveStatusBar */ true, mMediaViewHolder.getSeamlessButton(), - UserHandle.getUserHandleForUid(mUid)); + UserHandle.getUserHandleForUid(mUid), + mToken); } } else { mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId); @@ -775,7 +776,8 @@ public class MediaControlPanel { mPackageName, /* aboveStatusBar */ true, mMediaViewHolder.getSeamlessButton(), - UserHandle.getUserHandleForUid(mUid)); + UserHandle.getUserHandleForUid(mUid), + mToken); } } }); @@ -786,10 +788,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/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt index 2b5985882a6e..38377088a2d7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt @@ -709,12 +709,6 @@ constructor( // For Turbulence noise. val loadingEffectView = mediaViewHolder.loadingEffectView - turbulenceNoiseAnimationConfig = - createTurbulenceNoiseConfig( - loadingEffectView, - turbulenceNoiseView, - colorSchemeTransition - ) noiseDrawCallback = object : PaintDrawCallback { override fun onDraw(paint: Paint) { @@ -809,6 +803,14 @@ constructor( fun setUpTurbulenceNoise() { if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!this::turbulenceNoiseAnimationConfig.isInitialized) { + turbulenceNoiseAnimationConfig = + createTurbulenceNoiseConfig( + mediaViewHolder.loadingEffectView, + mediaViewHolder.turbulenceNoiseView, + colorSchemeTransition + ) + } if (Flags.shaderlibLoadingEffectRefactor()) { if (!this::loadingEffect.isInitialized) { loadingEffect = 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..4e9093642c6b 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,46 +54,32 @@ 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 -> + val mediaList = 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 (!allowReorder || !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 (allowReorder) { + if (modelsPendingRemoval.size > 0) { + updateHostVisibility() + } + modelsPendingRemoval.clear() } + allowReorder = false + + mediaList } .stateIn( scope = applicationScope, @@ -107,6 +87,8 @@ constructor( initialValue = emptyList(), ) + var updateHostVisibility: () -> Unit = {} + private val mediaControlByInstanceId = mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>() @@ -114,13 +96,18 @@ constructor( private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf() - private var shouldReorder = true + private var allowReorder = false fun onSwipeToDismiss() { logger.logSwipeDismiss() interactor.onSwipeToDismiss() } + fun onReorderingAllowed() { + allowReorder = true + interactor.reorderMedia() + } + private fun toViewModel( commonModel: MediaCommonModel.MediaControl ): MediaCommonViewModel.MediaControl { @@ -138,6 +125,7 @@ constructor( mediaControlByInstanceId.remove(instanceId) }, onUpdated = { onMediaControlAddedOrUpdated(it, commonModel) }, + isMediaFromRec = commonModel.isMediaFromRec ) .also { mediaControlByInstanceId[instanceId] = it } } @@ -213,7 +201,11 @@ constructor( ) { if (immediatelyRemove || isReorderingAllowed()) { interactor.dismissSmartspaceRecommendation(commonModel.recsLoadingModel.key, 0L) - // TODO if not immediate remove update host visibility + if (!immediatelyRemove) { + // Although it wasn't requested, we were able to process the removal + // immediately since reordering is allowed. So, notify hosts to update + updateHostVisibility() + } } else { modelsPendingRemoval.add(commonModel) } 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/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt index bc364c36a298..099991d7c671 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt @@ -20,6 +20,7 @@ import android.content.Context import android.content.pm.PackageManager import android.media.session.MediaController import android.media.session.MediaSession.Token +import android.media.session.PlaybackState import android.text.TextUtils import android.util.Log import androidx.constraintlayout.widget.ConstraintSet @@ -40,16 +41,14 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.monet.ColorScheme import com.android.systemui.monet.Style import com.android.systemui.res.R -import com.android.systemui.util.kotlin.sample import java.util.concurrent.Executor import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map /** Models UI state and handles user input for a media control. */ class MediaControlViewModel( @@ -60,31 +59,20 @@ class MediaControlViewModel( private val logger: MediaUiEventLogger, ) { - private val isAnyButtonClicked: MutableStateFlow<Boolean> = MutableStateFlow(false) - - private val playTurbulenceNoise: Flow<Boolean> = - interactor.mediaControl.sample( - combine(isAnyButtonClicked, interactor.isStartedPlaying) { - isButtonClicked, - isStartedPlaying -> - isButtonClicked && isStartedPlaying - } - .distinctUntilChanged() - ) - @OptIn(ExperimentalCoroutinesApi::class) val player: Flow<MediaPlayerViewModel?> = interactor.onAnyMediaConfigurationChange .flatMapLatest { - combine(playTurbulenceNoise, interactor.mediaControl) { - playTurbulenceNoise, - mediaControl -> - mediaControl?.let { toViewModel(it, playTurbulenceNoise) } + interactor.mediaControl.map { mediaControl -> + mediaControl?.let { toViewModel(it) } } } .distinctUntilChanged() .flowOn(backgroundDispatcher) + private var isPlaying = false + private var isAnyButtonClicked = false + private fun onDismissMediaData( token: Token?, uid: Int, @@ -95,10 +83,8 @@ class MediaControlViewModel( interactor.removeMediaControl(token, instanceId, MEDIA_PLAYER_ANIMATION_DELAY) } - private suspend fun toViewModel( - model: MediaControlModel, - playTurbulenceNoise: Boolean - ): MediaPlayerViewModel? { + private suspend fun toViewModel(model: MediaControlModel): MediaPlayerViewModel? { + val mediaController = model.token?.let { MediaController(applicationContext, it) } val wallpaperColors = MediaArtworkHelper.getWallpaperColor( applicationContext, @@ -118,8 +104,14 @@ class MediaControlViewModel( val gutsViewModel = toGutsViewModel(model, scheme) + // Set playing state + val wasPlaying = isPlaying + isPlaying = + mediaController?.playbackState?.let { it.state == PlaybackState.STATE_PLAYING } ?: false + // Resetting button clicks state. - isAnyButtonClicked.value = false + val wasButtonClicked = isAnyButtonClicked + isAnyButtonClicked = false return MediaPlayerViewModel( contentDescription = { gutsVisible -> @@ -144,7 +136,7 @@ class MediaControlViewModel( shouldAddGradient = wallpaperColors != null, colorScheme = scheme, canShowTime = canShowScrubbingTimeViews(model.semanticActionButtons), - playTurbulenceNoise = playTurbulenceNoise, + playTurbulenceNoise = isPlaying && !wasPlaying && wasButtonClicked, useSemanticActions = model.semanticActionButtons != null, actionButtons = toActionViewModels(model), outputSwitcher = toOutputSwitcherViewModel(model), @@ -168,9 +160,7 @@ class MediaControlViewModel( seekBarViewModel.updateStaticProgress(model.resumeProgress) } else { backgroundExecutor.execute { - seekBarViewModel.updateController( - model.token?.let { MediaController(applicationContext, it) } - ) + seekBarViewModel.updateController(mediaController) } } } @@ -241,12 +231,20 @@ class MediaControlViewModel( ) } else { logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId) - interactor.startMediaOutputDialog(expandable, model.packageName) + interactor.startMediaOutputDialog( + expandable, + model.packageName, + model.token + ) } } else { logger.logOpenOutputSwitcher(model.uid, model.packageName, model.instanceId) device?.intent?.let { interactor.startDeviceIntent(it) } - ?: interactor.startMediaOutputDialog(expandable, model.packageName) + ?: interactor.startMediaOutputDialog( + expandable, + model.packageName, + model.token + ) } } ) @@ -283,16 +281,17 @@ class MediaControlViewModel( ) } - private fun toActionViewModels(model: MediaControlModel): List<MediaActionViewModel?> { + private fun toActionViewModels(model: MediaControlModel): List<MediaActionViewModel> { val semanticActionButtons = model.semanticActionButtons?.let { mediaButton -> - with(mediaButton) { - val isScrubbingTimeEnabled = canShowScrubbingTimeViews(mediaButton) - SEMANTIC_ACTIONS_ALL.map { buttonId -> - getActionById(buttonId)?.let { - toSemanticActionViewModel(model, it, buttonId, isScrubbingTimeEnabled) - } - } + val isScrubbingTimeEnabled = canShowScrubbingTimeViews(mediaButton) + SEMANTIC_ACTIONS_ALL.map { buttonId -> + toSemanticActionViewModel( + model, + mediaButton.getActionById(buttonId), + buttonId, + isScrubbingTimeEnabled + ) } } val notifActionButtons = @@ -304,7 +303,7 @@ class MediaControlViewModel( private fun toSemanticActionViewModel( model: MediaControlModel, - mediaAction: MediaAction, + mediaAction: MediaAction?, buttonId: Int, canShowScrubbingTimeViews: Boolean ): MediaActionViewModel { @@ -312,9 +311,9 @@ class MediaControlViewModel( val hideWhenScrubbing = SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.contains(buttonId) val shouldHideWhenScrubbing = canShowScrubbingTimeViews && hideWhenScrubbing return MediaActionViewModel( - icon = mediaAction.icon, - contentDescription = mediaAction.contentDescription, - background = mediaAction.background, + icon = mediaAction?.icon, + contentDescription = mediaAction?.contentDescription, + background = mediaAction?.background, isVisibleWhenScrubbing = !shouldHideWhenScrubbing, notVisibleValue = if ( @@ -326,11 +325,11 @@ class MediaControlViewModel( ConstraintSet.GONE }, showInCollapsed = showInCollapsed, - rebindId = mediaAction.rebindId, + rebindId = mediaAction?.rebindId, buttonId = buttonId, - isEnabled = mediaAction.action != null, + isEnabled = mediaAction?.action != null, onClicked = { id -> - mediaAction.action?.let { + mediaAction?.action?.let { onButtonClicked(id, model.uid, model.packageName, model.instanceId, it) } }, @@ -366,7 +365,7 @@ class MediaControlViewModel( ) { logger.logTapAction(id, uid, packageName, instanceId) // TODO (b/330897926) log smartspace card reported (SMARTSPACE_CARD_CLICK_EVENT) - isAnyButtonClicked.value = true + isAnyButtonClicked = true action.run() } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt index d1014e83ea11..433434129b96 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaPlayerViewModel.kt @@ -35,7 +35,7 @@ data class MediaPlayerViewModel( val canShowTime: Boolean, val playTurbulenceNoise: Boolean, val useSemanticActions: Boolean, - val actionButtons: List<MediaActionViewModel?>, + val actionButtons: List<MediaActionViewModel>, val outputSwitcher: MediaOutputSwitcherViewModel, val gutsMenu: GutsViewModel, val onClicked: (Expandable) -> Unit, diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt index ff8e903b6637..0a717adc5162 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/LocalMediaManagerFactory.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.util import android.content.Context +import android.media.session.MediaSession import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.media.InfoMediaManager import com.android.settingslib.media.LocalMediaManager @@ -30,10 +31,16 @@ constructor( private val localBluetoothManager: LocalBluetoothManager? ) { /** Creates a [LocalMediaManager] for the given package. */ - fun create(packageName: String?): LocalMediaManager { + fun create(packageName: String?, token: MediaSession.Token? = null): LocalMediaManager { // TODO: b/321969740 - Populate the userHandle parameter in InfoMediaManager. The user // handle is necessary to disambiguate the same package running on different users. - return InfoMediaManager.createInstance(context, packageName, null, localBluetoothManager) + return InfoMediaManager.createInstance( + context, + packageName, + null, + localBluetoothManager, + token + ) .run { LocalMediaManager(context, localBluetoothManager, this, packageName) } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 6a6eba163a40..1e7bc0cacf1d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt @@ -54,5 +54,6 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClass fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled /** Check whether to use media refactor code */ - fun isMediaControlsRefactorEnabled() = MediaControlsRefactorFlag.isEnabled + fun isMediaControlsRefactorEnabled() = + MediaControlsRefactorFlag.isEnabled && SceneContainerFlag.isEnabled } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt index 06267e243456..6ef9ea36882b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogManager.kt @@ -40,7 +40,12 @@ constructor( // TODO: b/321969740 - Populate the userHandle parameter. The user handle is necessary to // disambiguate the same package running on different users. - val controller = mediaOutputControllerFactory.create(packageName, /* userHandle= */ null) + val controller = + mediaOutputControllerFactory.create( + packageName, + /* userHandle= */ null, + /* token */ null, + ) val dialog = MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller) mediaOutputBroadcastDialog = dialog diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index d6ca32079b09..c2cfdbe410b8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -78,6 +78,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.media.InfoMediaManager; import com.android.settingslib.media.LocalMediaManager; import com.android.settingslib.media.MediaDevice; +import com.android.settingslib.media.flags.Flags; import com.android.settingslib.utils.ThreadUtils; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogTransitionAnimator; @@ -141,6 +142,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, private final KeyguardManager mKeyGuardManager; private final NearbyMediaDevicesManager mNearbyMediaDevicesManager; private final Map<String, Integer> mNearbyDeviceInfoMap = new ConcurrentHashMap<>(); + private final MediaSession.Token mToken; @VisibleForTesting boolean mIsRefreshing = false; @@ -179,6 +181,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, Context context, @Assisted String packageName, @Assisted @Nullable UserHandle userHandle, + @Assisted @Nullable MediaSession.Token token, MediaSessionManager mediaSessionManager, @Nullable LocalBluetoothManager lbm, ActivityStarter starter, @@ -202,8 +205,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mKeyGuardManager = keyGuardManager; mFeatureFlags = featureFlags; mUserTracker = userTracker; + mToken = token; InfoMediaManager imm = - InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm); + InfoMediaManager.createInstance(mContext, packageName, userHandle, lbm, token); mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName); mDialogTransitionAnimator = dialogTransitionAnimator; @@ -235,7 +239,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, @AssistedFactory public interface Factory { /** Construct a MediaOutputController */ - MediaOutputController create(String packageName, UserHandle userHandle); + MediaOutputController create( + String packageName, UserHandle userHandle, MediaSession.Token token); } protected void start(@NonNull Callback cb) { @@ -297,23 +302,28 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } private MediaController getMediaController() { - for (NotificationEntry entry : mNotifCollection.getAllNotifs()) { - final Notification notification = entry.getSbn().getNotification(); - if (notification.isMediaNotification() - && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) { - MediaSession.Token token = notification.extras.getParcelable( - Notification.EXTRA_MEDIA_SESSION, - MediaSession.Token.class); - return new MediaController(mContext, token); + if (mToken != null && Flags.usePlaybackInfoForRoutingControls()) { + return new MediaController(mContext, mToken); + } else { + for (NotificationEntry entry : mNotifCollection.getAllNotifs()) { + final Notification notification = entry.getSbn().getNotification(); + if (notification.isMediaNotification() + && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) { + MediaSession.Token token = + notification.extras.getParcelable( + Notification.EXTRA_MEDIA_SESSION, MediaSession.Token.class); + return new MediaController(mContext, token); + } } - } - for (MediaController controller : mMediaSessionManager.getActiveSessionsForUser(null, - mUserTracker.getUserHandle())) { - if (TextUtils.equals(controller.getPackageName(), mPackageName)) { - return controller; + for (MediaController controller : + mMediaSessionManager.getActiveSessionsForUser( + null, mUserTracker.getUserHandle())) { + if (TextUtils.equals(controller.getPackageName(), mPackageName)) { + return controller; + } } + return null; } - return null; } @Override @@ -869,10 +879,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mMetricLogger.logInteractionUnmute(device); } - String getPackageName() { - return mPackageName; - } - boolean hasAdjustVolumeUserRestriction() { if (RestrictedLockUtilsInternal.checkIfRestrictionEnforced( mContext, UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId()) != null) { @@ -955,6 +961,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, mContext, mPackageName, mUserHandle, + mToken, mMediaSessionManager, mLocalBluetoothManager, mActivityStarter, @@ -1060,7 +1067,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, boolean isBroadcastSupported() { LocalBluetoothLeBroadcast broadcast = mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); - return broadcast != null ? true : false; + return broadcast != null; } boolean isBluetoothLeBroadcastEnabled() { @@ -1194,13 +1201,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, assistant.unregisterServiceCallBack(callback); } - private boolean isPlayBackInfoLocal() { - return mMediaController != null - && mMediaController.getPlaybackInfo() != null - && mMediaController.getPlaybackInfo().getPlaybackType() - == MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL; - } - boolean isPlaying() { if (mMediaController == null) { return false; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt index 04d1492ff656..ee8169423de2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogManager.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.dialog import android.content.Context +import android.media.session.MediaSession import android.os.UserHandle import android.view.View import com.android.internal.jank.InteractionJankMonitor @@ -49,7 +50,8 @@ constructor( packageName: String, aboveStatusBar: Boolean, view: View? = null, - userHandle: UserHandle? = null + userHandle: UserHandle? = null, + token: MediaSession.Token? = null ) { createAndShowWithController( packageName, @@ -65,6 +67,7 @@ constructor( ) }, userHandle = userHandle, + token = token, ) } @@ -77,6 +80,7 @@ constructor( aboveStatusBar: Boolean, controller: DialogTransitionAnimator.Controller?, userHandle: UserHandle? = null, + token: MediaSession.Token? = null, ) { createAndShow( packageName, @@ -84,6 +88,7 @@ constructor( dialogTransitionAnimatorController = controller, includePlaybackAndAppMetadata = true, userHandle = userHandle, + token = token, ) } @@ -108,11 +113,12 @@ constructor( dialogTransitionAnimatorController: DialogTransitionAnimator.Controller?, includePlaybackAndAppMetadata: Boolean = true, userHandle: UserHandle? = null, + token: MediaSession.Token? = null, ) { // Dismiss the previous dialog, if any. mediaOutputDialog?.dismiss() - val controller = mediaOutputControllerFactory.create(packageName, userHandle) + val controller = mediaOutputControllerFactory.create(packageName, userHandle, token) val mediaOutputDialog = MediaOutputDialog( diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java index 9cc288899d45..846460edbe9e 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSwitcherDialogUI.java @@ -56,7 +56,11 @@ public class MediaOutputSwitcherDialogUI implements CoreStartable, CommandQueue. public void showMediaOutputSwitcher(String packageName, UserHandle userHandle) { if (!TextUtils.isEmpty(packageName)) { mMediaOutputDialogManager.createAndShow( - packageName, /* aboveStatusBar= */ false, /* view= */ null, userHandle); + packageName, + /* aboveStatusBar= */ false, + /* view= */ null, + userHandle, + /* token */ null); } else { Log.e(TAG, "Unable to launch media output dialog. Package name is empty."); } 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/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt index 412c006806bf..9265bfb2f66b 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt @@ -140,10 +140,11 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 val bitmapShader = bitmapShader ?: return val thumbnailData = thumbnailData ?: return + val thumbnail = thumbnailData.thumbnail ?: return val display = context.display ?: return val windowMetrics = windowManager.maximumWindowMetrics - previewRect.set(0, 0, thumbnailData.thumbnail.width, thumbnailData.thumbnail.height) + previewRect.set(0, 0, thumbnail.width, thumbnail.height) val currentRotation: Int = display.rotation val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt index 170481233504..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,14 +49,14 @@ 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 } val transitionState = sceneInteractor.get().transitionState.value val idleTransitionStateOrNull = transitionState as? ObservableTransitionState.Idle - val currentSceneOrNull = idleTransitionStateOrNull?.scene + val currentSceneOrNull = idleTransitionStateOrNull?.currentScene val invisibleDueToOcclusion = occlusionInteractor.get().invisibleDueToOcclusion.value return currentSceneOrNull?.let { sceneKey -> EvaluatorByFlag[flag]?.invoke( @@ -79,10 +80,16 @@ 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 { it.scene == Scenes.Shade }, - SYSUI_STATE_QUICK_SETTINGS_EXPANDED to { it.scene == Scenes.QuickSettings }, + SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to + { + it.scene == Scenes.NotificationsShade || it.scene == Scenes.Shade + }, + SYSUI_STATE_QUICK_SETTINGS_EXPANDED to + { + it.scene == Scenes.QuickSettingsShade || it.scene == Scenes.QuickSettings + }, SYSUI_STATE_BOUNCER_SHOWING to { it.scene == Scenes.Bouncer }, SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING 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..67fe0e981b09 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,17 @@ public class SysUiState implements Dumpable { } /** Returns the current sysui state flags. */ - public int getFlags() { + @SystemUiStateFlags + public long getFlags() { return mFlags; } + public boolean isFlagEnabled(@SystemUiStateFlags long flag) { + return (mFlags & flag) != 0; + } + /** 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 +97,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 +111,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 +143,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/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt new file mode 100644 index 000000000000..f677ec1b31bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeSceneViewModel.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.notifications.ui.viewmodel + +import com.android.compose.animation.scene.Back +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** Models UI state and handles user input for the Notifications Shade scene. */ +@SysUISingleton +class NotificationsShadeSceneViewModel +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + overlayShadeViewModel: OverlayShadeViewModel, +) { + val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = + overlayShadeViewModel.backgroundScene + .map(::destinationScenes) + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = destinationScenes(overlayShadeViewModel.backgroundScene.value), + ) + + private fun destinationScenes(backgroundScene: SceneKey): Map<UserAction, UserActionResult> { + return mapOf( + Swipe.Up to backgroundScene, + Back to backgroundScene, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index d476e6302a3b..a14479bb848e 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -67,6 +67,7 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.systemui.SystemUIApplication; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.ActivityStarter; @@ -323,7 +324,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { // remaining estimate is disabled if (!mCurrentBatterySnapshot.isHybrid() || mBucket < -1 || mCurrentBatterySnapshot.getTimeRemainingMillis() - < mCurrentBatterySnapshot.getSevereThresholdMillis()) { + < mCurrentBatterySnapshot.getSevereThresholdMillis()) { nb.setColor(Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorError)); } @@ -703,17 +704,23 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { mSaverConfirmation = null; logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_DISMISS); }); - WeakReference<View> ref = mBatteryControllerLazy.get().getLastPowerSaverStartView(); - if (ref != null && ref.get() != null && ref.get().isAggregatedVisible()) { - mDialogTransitionAnimator.showFromView(d, ref.get(), + WeakReference<Expandable> ref = + mBatteryControllerLazy.get().getLastPowerSaverStartExpandable(); + if (ref != null && ref.get() != null) { + DialogTransitionAnimator.Controller controller = ref.get().dialogTransitionController( new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)); + if (controller != null) { + mDialogTransitionAnimator.show(d, controller); + } else { + d.show(); + } } else { d.show(); } logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_DIALOG); mSaverConfirmation = d; - mBatteryControllerLazy.get().clearLastPowerSaverStartView(); + mBatteryControllerLazy.get().clearLastPowerSaverStartExpandable(); } @VisibleForTesting @@ -873,4 +880,4 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } } } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt index 3907a7240258..5e6ee4d3c700 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/dagger/QRCodeScannerModule.kt @@ -16,12 +16,20 @@ package com.android.systemui.qrcodescanner.dagger +import com.android.systemui.Flags import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.qs.tiles.QRCodeScannerTile +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory +import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel +import com.android.systemui.qs.tiles.viewmodel.StubQSTileViewModel import com.android.systemui.res.R import dagger.Binds import dagger.Module @@ -54,5 +62,24 @@ interface QRCodeScannerModule { ), instanceId = uiEventLogger.getNewInstanceId(), ) + + /** Inject QR Code Scanner Tile into tileViewModelMap in QSModule. */ + @Provides + @IntoMap + @StringKey(QR_CODE_SCANNER_TILE_SPEC) + fun provideQRCodeScannerTileViewModel( + factory: QSTileViewModelFactory.Static<QRCodeScannerTileModel>, + mapper: QRCodeScannerTileMapper, + stateInteractor: QRCodeScannerTileDataInteractor, + userActionInteractor: QRCodeScannerTileUserActionInteractor + ): QSTileViewModel = + if (Flags.qsNewTilesFuture()) + factory.create( + TileSpec.create(QR_CODE_SCANNER_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + else StubQSTileViewModel } } 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/TileStateToProto.kt b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt index 2c8a5a4981d0..1336d640a9e5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/TileStateToProto.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs import android.service.quicksettings.Tile import android.text.TextUtils +import android.widget.Switch import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.external.CustomTile import com.android.systemui.qs.nano.QsTileState @@ -44,8 +45,8 @@ fun QSTile.State.toProto(): QsTileState? { } label?.let { state.label = it.toString() } secondaryLabel?.let { state.secondaryLabel = it.toString() } - if (this is QSTile.BooleanState) { - state.booleanState = value + if (expandedAccessibilityClassName == Switch::class.java.name) { + state.booleanState = state.state == QsTileState.ACTIVE } return state } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index b705a0389300..ea89be61d773 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -21,6 +21,7 @@ import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE; import android.content.Context; import android.os.Handler; +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogModule; import com.android.systemui.dagger.NightDisplayListenerModule; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; @@ -60,6 +61,7 @@ import javax.inject.Named; */ @Module(subcomponents = {QSFragmentComponent.class, QSSceneComponent.class}, includes = { + BluetoothTileDialogModule.class, MediaModule.class, PanelsModule.class, QSExternalModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index b53c2450f4c7..5d35a69be910 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -41,8 +41,8 @@ import android.service.quicksettings.TileService; import android.text.format.DateUtils; import android.util.Log; import android.view.IWindowManager; -import android.view.View; import android.view.WindowManagerGlobal; +import android.widget.Button; import android.widget.Switch; import androidx.annotation.Nullable; @@ -52,6 +52,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.animation.ActivityTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -99,7 +100,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener, @Nullable private CharSequence mDefaultLabel; @Nullable - private View mViewClicked; + private Expandable mExpandableClicked; private final Context mUserContext; @@ -347,7 +348,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener, mService.onStartListening(); } } else { - mViewClicked = null; + mExpandableClicked = null; mService.onStopListening(); if (mIsTokenGranted && !mIsShowingDialog) { try { @@ -409,11 +410,11 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener, } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (mTile.getState() == Tile.STATE_UNAVAILABLE) { return; } - mViewClicked = view; + mExpandableClicked = expandable; try { if (DEBUG) Log.d(TAG, "Adding token"); mWindowManager.addWindowToken(mToken, TYPE_QS_DIALOG, @@ -502,6 +503,8 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener, if (state instanceof BooleanState) { state.expandedAccessibilityClassName = Switch.class.getName(); ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE); + } else { + state.expandedAccessibilityClassName = Button.class.getName(); } } @@ -541,11 +544,9 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener, Log.i(TAG, "The activity is starting"); ActivityTransitionAnimator.Controller controller = - mViewClicked == null ? null : - ActivityTransitionAnimator.Controller.fromView( - mViewClicked, - InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE - ); + mExpandableClicked == null ? null : + mExpandableClicked.activityTransitionController( + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); mActivityStarter.startPendingIntentMaybeDismissingKeyguard( pendingIntent, /* intentSentUiThreadCallback= */ null, 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 880289e88813..24b7a011f093 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -15,8 +15,12 @@ */ package com.android.systemui.qs.external; +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; @@ -31,8 +35,10 @@ import android.net.Uri; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.os.IDeviceIdleController; import android.os.RemoteException; import android.os.UserHandle; +import android.provider.DeviceConfig; import android.service.quicksettings.IQSService; import android.service.quicksettings.IQSTileService; import android.service.quicksettings.TileService; @@ -84,12 +90,15 @@ 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; private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS; private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS; - + private static final long TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS = 15_000; + private static final String PROPERTY_TILE_SERVICE_ONCLICK_ALLOW_LIST_DURATION = + "property_tile_service_onclick_allow_list_duration"; // Shared prefs that hold tile lifecycle info. private static final String TILES = "tiles_prefs"; @@ -102,6 +111,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements private final PackageManagerAdapter mPackageManagerAdapter; private final BroadcastDispatcher mBroadcastDispatcher; private final ActivityManager mActivityManager; + private final IDeviceIdleController mDeviceIdleController; private Set<Integer> mQueuedMessages = new ArraySet<>(); @NonNull @@ -120,12 +130,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements private TileChangeListener mChangeListener; // Return value from bindServiceAsUser, determines whether safe to call unbind. private AtomicBoolean mIsBound = new AtomicBoolean(false); + private long mTempAllowFgsLaunchDuration = TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS; + private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener; + private AtomicBoolean mDeviceConfigChangedListenerRegistered = new AtomicBoolean(false); @AssistedInject TileLifecycleManager(@Main Handler handler, Context context, IQSService service, PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher, @Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager, - @Background DelayableExecutor executor) { + IDeviceIdleController deviceIdleController, @Background DelayableExecutor executor) { mContext = context; mHandler = handler; mIntent = intent; @@ -136,6 +149,16 @@ public class TileLifecycleManager extends BroadcastReceiver implements mPackageManagerAdapter = packageManagerAdapter; mBroadcastDispatcher = broadcastDispatcher; mActivityManager = activityManager; + mDeviceIdleController = deviceIdleController; + mDeviceConfigChangedListener = properties -> { + if (!DeviceConfig.NAMESPACE_SYSTEMUI.equals(properties.getNamespace())) { + return; + } + mTempAllowFgsLaunchDuration = properties.getLong( + PROPERTY_TILE_SERVICE_ONCLICK_ALLOW_LIST_DURATION, + TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS); + }; + if (mDebug) Log.d(TAG, "Creating " + mIntent + " " + mUser); } @@ -211,6 +234,13 @@ public class TileLifecycleManager extends BroadcastReceiver implements } mBound.set(bind); if (bind) { + if (mDeviceConfigChangedListenerRegistered.compareAndSet(false, true)) { + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI, mExecutor, + mDeviceConfigChangedListener); + mTempAllowFgsLaunchDuration = DeviceConfig.getLong(NAMESPACE_SYSTEMUI, + PROPERTY_TILE_SERVICE_ONCLICK_ALLOW_LIST_DURATION, + TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS); + } if (mBindTryCount == MAX_BIND_RETRIES) { // Too many failures, give up on this tile until an update. startPackageListening(); @@ -341,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) { @@ -363,6 +403,9 @@ public class TileLifecycleManager extends BroadcastReceiver implements stopPackageListening(); } mChangeListener = null; + if (mDeviceConfigChangedListener != null) { + DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); + } } /** @@ -556,17 +599,32 @@ 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(); + } } } @Override public void onClick(IBinder iBinder) { if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser); - if (isNullOrFailedAction(mOptionalWrapper, (wrapper) -> wrapper.onClick(iBinder))) { + if (isNullOrFailedAction(mOptionalWrapper, (wrapper) -> { + final String packageName = mIntent.getComponent().getPackageName(); + try { + mDeviceIdleController.addPowerSaveTempWhitelistApp(packageName, + mTempAllowFgsLaunchDuration, mUser.getIdentifier(), REASON_TILE_ONCLICK, + "tile onclick"); + } catch (RemoteException e) { + Log.d(TAG, "Caught exception trying to add client package to temp allow list", e); + } + return wrapper.onClick(iBinder); + })) { mClickBinder = iBinder; queueMessage(MSG_ON_CLICK); 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 ee4eeb8b9e32..2cc3985a88ad 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,22 +16,97 @@ 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.shared.model.GridLayoutTypeKey +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.shared.model.StretchedGridLayoutType import com.android.systemui.qs.panels.ui.compose.GridLayout import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.panels.ui.compose.StretchedGridLayout import dagger.Binds import dagger.Module -import dagger.multibindings.IntoMap +import dagger.Provides +import dagger.multibindings.IntoSet @Module interface PanelsModule { @Binds fun bindIconTilesRepository(impl: IconTilesRepositoryImpl): IconTilesRepository @Binds - @IntoMap - @GridLayoutTypeKey(InfiniteGridLayoutType::class) - fun bindGridLayout(impl: InfiniteGridLayout): GridLayout + 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) + } + + @Provides + @IntoSet + fun provideStretchedGridLayout( + gridLayout: StretchedGridLayout + ): Pair<GridLayoutType, GridLayout> { + return Pair(StretchedGridLayoutType, gridLayout) + } + + @Provides + fun provideGridLayoutMap( + entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>> + ): Map<GridLayoutType, GridLayout> { + return entries.toMap() + } + + @Provides + fun provideGridLayoutTypes( + entries: Set<@JvmSuppressWildcards Pair<GridLayoutType, GridLayout>> + ): Set<GridLayoutType> { + return entries.map { it.first }.toSet() + } + + @Provides + @IntoSet + fun provideGridConsistencyInteractor( + consistencyInteractor: InfiniteGridConsistencyInteractor + ): Pair<GridLayoutType, GridTypeConsistencyInteractor> { + return Pair(InfiniteGridLayoutType, consistencyInteractor) + } + + @Provides + @IntoSet + fun provideStretchedGridConsistencyInteractor( + consistencyInteractor: NoopGridConsistencyInteractor + ): Pair<GridLayoutType, GridTypeConsistencyInteractor> { + return Pair(StretchedGridLayoutType, 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..31795d59bb2b 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,23 @@ 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> + fun setLayout(type: 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() + + override fun setLayout(type: GridLayoutType) { + if (_layout.value != type) { + _layout.value = type + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt new file mode 100644 index 000000000000..28c1fbf2007d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepository.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.data.repository + +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.panels.shared.model.EditTileData +import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.settings.UserTracker +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +@SysUISingleton +class IconAndNameCustomRepository +@Inject +constructor( + private val installedTilesComponentRepository: InstalledTilesComponentRepository, + private val userTracker: UserTracker, + @Background private val backgroundContext: CoroutineContext, +) { + /** + * Returns a list of the icon/labels for all available (installed and enabled) tile services. + * + * No order is guaranteed. + */ + suspend fun getCustomTileData(): List<EditTileData> { + return withContext(backgroundContext) { + val installedTiles = + installedTilesComponentRepository.getInstalledTilesServiceInfos(userTracker.userId) + val packageManager = userTracker.userContext.packageManager + installedTiles + .map { + val tileSpec = TileSpec.create(it.componentName) + val label = it.loadLabel(packageManager) + val icon = it.loadIcon(packageManager) + val appName = it.applicationInfo.loadLabel(packageManager) + if (icon != null) { + EditTileData( + tileSpec, + Icon.Loaded(icon, ContentDescription.Loaded(label.toString())), + Text.Loaded(label.toString()), + Text.Loaded(appName.toString()), + ) + } else { + null + } + } + .filterNotNull() + } + } +} 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/data/repository/StockTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.kt new file mode 100644 index 000000000000..ec9d151a26d3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/StockTilesRepository.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.panels.data.repository + +import android.content.res.Resources +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.res.R +import javax.inject.Inject + +@SysUISingleton +class StockTilesRepository +@Inject +constructor( + @Main private val resources: Resources, +) { + /** + * List of stock platform tiles. All of the specs will be of type [TileSpec.PlatformTileSpec]. + */ + val stockTiles = + resources + .getString(R.string.quick_settings_tiles_stock) + .split(",") + .map(TileSpec::create) + .filterNot { it is TileSpec.Invalid } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt new file mode 100644 index 000000000000..3b29422ccfc3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractor.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.data.repository.IconAndNameCustomRepository +import com.android.systemui.qs.panels.data.repository.StockTilesRepository +import com.android.systemui.qs.panels.domain.model.EditTilesModel +import com.android.systemui.qs.panels.shared.model.EditTileData +import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider +import javax.inject.Inject + +@SysUISingleton +class EditTilesListInteractor +@Inject +constructor( + private val stockTilesRepository: StockTilesRepository, + private val qsTileConfigProvider: QSTileConfigProvider, + private val iconAndNameCustomRepository: IconAndNameCustomRepository, +) { + /** + * Provides a list of the tiles to edit, with their UI information (icon, labels). + * + * The icons have the label as their content description. + */ + suspend fun getTilesToEdit(): EditTilesModel { + val stockTiles = + stockTilesRepository.stockTiles.map { + if (qsTileConfigProvider.hasConfig(it.spec)) { + val config = qsTileConfigProvider.getConfig(it.spec) + EditTileData( + it, + Icon.Resource( + config.uiConfig.iconRes, + ContentDescription.Resource(config.uiConfig.labelRes) + ), + Text.Resource(config.uiConfig.labelRes), + null, + ) + } else { + EditTileData( + it, + Icon.Resource( + android.R.drawable.star_on, + ContentDescription.Loaded(it.spec) + ), + Text.Loaded(it.spec), + null + ) + } + } + return EditTilesModel(stockTiles, iconAndNameCustomRepository.getCustomTileData()) + } +} 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/GridLayoutTypeInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt index b6be5780bb60..4af1b2223c4c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/GridLayoutTypeInteractor.kt @@ -20,9 +20,13 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.qs.panels.data.repository.GridLayoutTypeRepository import com.android.systemui.qs.panels.shared.model.GridLayoutType import javax.inject.Inject -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow @SysUISingleton -class GridLayoutTypeInteractor @Inject constructor(repo: GridLayoutTypeRepository) { - val layout: Flow<GridLayoutType> = repo.layout +class GridLayoutTypeInteractor @Inject constructor(private val repo: GridLayoutTypeRepository) { + val layout: StateFlow<GridLayoutType> = repo.layout + + fun setLayoutType(type: GridLayoutType) { + repo.setLayout(type) + } } 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..b437f645d4bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.domain.interactor + +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.shared.model.TileRow +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<TileSpec>(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.tiles.map { it.tile }) + 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<TileSpec>? = tilesQueue.firstOrNull { it.width == 1 } + if (iconTile != null) { + tilesQueue.remove(iconTile) + tilesQueue.addFirst(tile) + row.maybeAddTile(iconTile) + } else { + val tileToRemove: SizedTile<TileSpec>? = 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.tiles.map { it.tile } + 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.tiles.map { it.tile }) + + return newTiles.toList() + } + + 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/NoopConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.kt new file mode 100644 index 000000000000..97ceacc6926d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/NoopConsistencyInteractor.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.qs.panels.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject + +@SysUISingleton +class NoopConsistencyInteractor @Inject constructor() : GridTypeConsistencyInteractor { + override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> = tiles +} 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/domain/model/EditTilesModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/model/EditTilesModel.kt new file mode 100644 index 000000000000..b573b9a8770f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/model/EditTilesModel.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.model + +import com.android.systemui.qs.panels.shared.model.EditTileData + +data class EditTilesModel( + val stockTiles: List<EditTileData>, + val customTiles: List<EditTileData>, +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt new file mode 100644 index 000000000000..8b70bb9f9e23 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/EditTileData.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.shared.model + +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.qs.pipeline.shared.TileSpec + +data class EditTileData( + val tileSpec: TileSpec, + val icon: Icon, + val label: Text, + val appName: Text?, +) { + init { + check( + (tileSpec is TileSpec.PlatformTileSpec && appName == null) || + (tileSpec is TileSpec.CustomTileSpec && appName != null) + ) { + "tileSpec: $tileSpec - appName: $appName. " + + "appName must be non-null for custom tiles and only for custom tiles." + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutTypeKey.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt index 0dbaaba9dfac..884cde35a1aa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutTypeKey.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridConsistencyLog.kt @@ -16,13 +16,9 @@ package com.android.systemui.qs.panels.shared.model -import dagger.MapKey -import kotlin.reflect.KClass +import javax.inject.Qualifier -/** - * Dagger map key to associate a [GridLayoutType] with its - * [com.android.systemui.qs.panels.ui.compose.GridLayout]. - */ +@Qualifier +@MustBeDocumented @Retention(AnnotationRetention.RUNTIME) -@MapKey -annotation class GridLayoutTypeKey(val value: KClass<out GridLayoutType>) +annotation class GridConsistencyLog() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt index 23110dcaa560..501730a7c8a3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/GridLayoutType.kt @@ -25,3 +25,9 @@ interface GridLayoutType /** Grid type representing a scrollable vertical grid. */ data object InfiniteGridLayoutType : GridLayoutType + +/** + * Grid type representing a scrollable vertical grid where tiles will stretch to fill in empty + * spaces. + */ +data object StretchedGridLayoutType : GridLayoutType diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt new file mode 100644 index 000000000000..7e4381bbff03 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/shared/model/TileRow.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.shared.model + +/** Represents a tile of type [T] associated with a width */ +data class SizedTile<T>(val tile: T, val width: Int) + +/** Represents a row of [SizedTile] with a maximum width of [columns] */ +class TileRow<T>(private val columns: Int) { + private var availableColumns = columns + private val _tiles: MutableList<SizedTile<T>> = mutableListOf() + val tiles: List<SizedTile<T>> + get() = _tiles.toList() + + fun maybeAddTile(tile: SizedTile<T>): Boolean { + if (availableColumns - tile.width >= 0) { + _tiles.add(tile) + availableColumns -= tile.width + return true + } + return false + } + + fun findLastIconTile(): SizedTile<T>? { + return _tiles.findLast { it.width == 1 } + } + + fun removeTile(tile: SizedTile<T>) { + _tiles.remove(tile) + availableColumns += tile.width + } + + fun clear() { + _tiles.clear() + availableColumns = columns + } + + fun isFull(): Boolean = availableColumns == 0 +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt new file mode 100644 index 000000000000..3bda7757f8e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel + +@Composable +fun EditMode( + viewModel: EditModeViewModel, + modifier: Modifier = Modifier, +) { + val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() + val tiles by viewModel.tiles.collectAsStateWithLifecycle(emptyList()) + + BackHandler { viewModel.stopEditing() } + + DisposableEffect(Unit) { onDispose { viewModel.stopEditing() } } + + Column(modifier) { + gridLayout.EditTileGrid( + tiles, + Modifier, + viewModel::addTile, + viewModel::removeTile, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt index 920cbe77e42f..8806931a888a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt @@ -18,13 +18,22 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec interface GridLayout { @Composable fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, - tile: @Composable (TileViewModel) -> Unit, + ) + + @Composable + fun EditTileGrid( + tiles: List<EditTileViewModel>, + modifier: Modifier, + onAddTile: (TileSpec, Int) -> Unit, + onRemoveTile: (TileSpec) -> Unit, ) } 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 4d0089e70e80..f5ee720faff6 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 @@ -16,52 +16,83 @@ package com.android.systemui.qs.panels.ui.compose -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.height import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource -import androidx.compose.ui.res.integerResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle +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.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.res.R import javax.inject.Inject -class InfiniteGridLayout @Inject constructor() : GridLayout { +@SysUISingleton +class InfiniteGridLayout +@Inject +constructor( + private val iconTilesInteractor: IconTilesInteractor, + private val gridSizeInteractor: InfiniteGridSizeInteractor +) : GridLayout { @Composable override fun TileGrid( tiles: List<TileViewModel>, modifier: Modifier, - tile: @Composable (TileViewModel) -> Unit ) { DisposableEffect(tiles) { val token = Any() tiles.forEach { it.startListening(token) } onDispose { tiles.forEach { it.stopListening(token) } } } + val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() - LazyVerticalGrid( - columns = - GridCells.Fixed( - integerResource(R.integer.quick_settings_infinite_grid_num_columns) - ), - verticalArrangement = - Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), - horizontalArrangement = - Arrangement.spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), - modifier = modifier - ) { - tiles.forEach { item(span = { it.span() }) { tile(it) } } + TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) { + items( + tiles.size, + span = { index -> + val iconOnly = iconTilesSpecs.contains(tiles[index].spec) + if (iconOnly) { + GridItemSpan(1) + } else { + GridItemSpan(2) + } + } + ) { index -> + Tile( + tiles[index], + iconTilesSpecs.contains(tiles[index].spec), + Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) + ) + } } } - private fun TileViewModel.span(): GridItemSpan = - if (iconOnly) { - GridItemSpan(1) - } else { - GridItemSpan(2) - } + @Composable + override fun EditTileGrid( + tiles: List<EditTileViewModel>, + modifier: Modifier, + onAddTile: (TileSpec, Int) -> Unit, + onRemoveTile: (TileSpec) -> Unit, + ) { + val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() + + DefaultEditTileGrid( + tiles = tiles, + iconOnlySpecs = iconOnlySpecs, + columns = GridCells.Fixed(columns), + modifier = modifier, + onAddTile = onAddTile, + onRemoveTile = onRemoveTile, + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt new file mode 100644 index 000000000000..ddd97c2e8944 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.compose + +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.dimensionResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle +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.shared.model.SizedTile +import com.android.systemui.qs.panels.shared.model.TileRow +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.res.R +import javax.inject.Inject + +@SysUISingleton +class StretchedGridLayout +@Inject +constructor( + private val iconTilesInteractor: IconTilesInteractor, + private val gridSizeInteractor: InfiniteGridSizeInteractor, +) : GridLayout { + + @Composable + override fun TileGrid( + tiles: List<TileViewModel>, + modifier: Modifier, + ) { + DisposableEffect(tiles) { + val token = Any() + tiles.forEach { it.startListening(token) } + onDispose { tiles.forEach { it.stopListening(token) } } + } + + // Tile widths [normal|stretched] + // Icon [3 | 4] + // Large [6 | 8] + val columns = 12 + val iconTilesSpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val stretchedTiles = + remember(tiles) { + val sizedTiles = + tiles.map { + SizedTile( + it, + if (iconTilesSpecs.contains(it.spec)) { + 3 + } else { + 6 + } + ) + } + splitInRows(sizedTiles, columns) + } + + TileLazyGrid(columns = GridCells.Fixed(columns), modifier = modifier) { + items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index -> + Tile( + stretchedTiles[index].tile, + iconTilesSpecs.contains(stretchedTiles[index].tile.spec), + Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) + ) + } + } + } + + @Composable + override fun EditTileGrid( + tiles: List<EditTileViewModel>, + modifier: Modifier, + onAddTile: (TileSpec, Int) -> Unit, + onRemoveTile: (TileSpec) -> Unit + ) { + val iconOnlySpecs by iconTilesInteractor.iconTilesSpecs.collectAsStateWithLifecycle() + val columns by gridSizeInteractor.columns.collectAsStateWithLifecycle() + + DefaultEditTileGrid( + tiles = tiles, + iconOnlySpecs = iconOnlySpecs, + columns = GridCells.Fixed(columns), + modifier = modifier, + onAddTile = onAddTile, + onRemoveTile = onRemoveTile, + ) + } + + private fun splitInRows( + tiles: List<SizedTile<TileViewModel>>, + columns: Int + ): List<SizedTile<TileViewModel>> { + val row = TileRow<TileViewModel>(columns) + + return buildList { + for (tile in tiles) { + if (row.maybeAddTile(tile)) { + if (row.isFull()) { + // Row is full, no need to stretch tiles + addAll(row.tiles) + row.clear() + } + } else { + if (row.isFull()) { + addAll(row.tiles) + } else { + // Stretching tiles when row isn't full + addAll(row.tiles.map { it.copy(width = it.width + (it.width / 3)) }) + } + row.clear() + row.maybeAddTile(tile) + } + } + addAll(row.tiles) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt index 35f29705fab2..eb45110533a4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -23,25 +23,39 @@ 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 import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridScope +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Remove +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -50,85 +64,263 @@ 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.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 androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.compose.animation.Expandable import com.android.compose.theme.colorAttr -import com.android.systemui.common.shared.model.Icon as IconModel +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.qs.panels.ui.viewmodel.ActiveTileColorAttributes +import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions +import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileColorAttributes import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.toUiState +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.mapLatest +object TileType + +@OptIn(ExperimentalCoroutinesApi::class, ExperimentalFoundationApi::class) @Composable fun Tile( - tileViewModel: TileViewModel, - modifier: Modifier = Modifier, + tile: TileViewModel, + iconOnly: Boolean, + modifier: Modifier, ) { - val state: TileUiState by tileViewModel.state.collectAsState(tileViewModel.currentState) + val state: TileUiState by + tile.state + .mapLatest { it.toUiState() } + .collectAsStateWithLifecycle(tile.currentState.toUiState()) val context = LocalContext.current - val horizontalAlignment = - if (state.iconOnly) { - Alignment.CenterHorizontally - } else { - Alignment.Start - } - Row( - modifier = - modifier - .fillMaxWidth() - .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))) - .clickable { tileViewModel.onClick(null) } - .background(colorAttr(state.colors.background)) - .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin)), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = - Arrangement.spacedBy( - space = dimensionResource(id = R.dimen.qs_label_container_margin), - alignment = horizontalAlignment - ) + 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) { - IconModel.Resource(it.resId, null) - } else { - IconModel.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 + ) + } + } +} + +@Composable +fun TileLazyGrid( + modifier: Modifier = Modifier, + columns: GridCells, + content: LazyGridScope.() -> Unit, +) { + LazyVerticalGrid( + columns = columns, + verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)), + horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)), + modifier = modifier, + content = content, + ) +} + +@Composable +fun DefaultEditTileGrid( + tiles: List<EditTileViewModel>, + iconOnlySpecs: Set<TileSpec>, + columns: GridCells, + modifier: Modifier, + onAddTile: (TileSpec, Int) -> Unit, + onRemoveTile: (TileSpec) -> Unit, +) { + val (currentTiles, otherTiles) = tiles.partition { it.isCurrent } + val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null } + val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState { + onAddTile(it, CurrentTilesInteractor.POSITION_AT_END) + } + val isIconOnly: (TileSpec) -> Boolean = + remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } } + + TileLazyGrid(modifier = modifier, columns = columns) { + // These Text are just placeholders to see the different sections. Not final UI. + item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) } + + editTiles( + currentTiles, + ClickAction.REMOVE, + onRemoveTile, + isIconOnly, + indicatePosition = true, + ) + + item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) } + + editTiles( + otherTilesStock, + ClickAction.ADD, + addTileToEnd, + isIconOnly, + ) + + item(span = { GridItemSpan(maxLineSpan) }) { + Text("Custom tiles to add", color = Color.White) + } + + editTiles( + otherTilesCustom, + ClickAction.ADD, + addTileToEnd, + isIconOnly, + ) + } +} + +private fun LazyGridScope.editTiles( + tiles: List<EditTileViewModel>, + clickAction: ClickAction, + onClick: (TileSpec) -> Unit, + isIconOnly: (TileSpec) -> Boolean, + indicatePosition: Boolean = false, +) { + items( + count = tiles.size, + key = { tiles[it].tileSpec.spec }, + span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) }, + contentType = { TileType } + ) { + val viewModel = tiles[it] + val canClick = + when (clickAction) { + ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions + ClickAction.REMOVE -> AvailableEditActions.REMOVE in viewModel.availableEditActions + } + val onClickActionName = + when (clickAction) { + ClickAction.ADD -> + stringResource(id = R.string.accessibility_qs_edit_tile_add_action) + ClickAction.REMOVE -> + stringResource(id = R.string.accessibility_qs_edit_remove_tile_action) + } + val stateDescription = + if (indicatePosition) { + stringResource(id = R.string.accessibility_qs_edit_position, it + 1) + } else { + "" } - TileIcon(icon, colorAttr(state.colors.icon)) - if (!state.iconOnly) { - Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) { - Text( - state.label.toString(), - color = colorAttr(state.colors.label), - modifier = Modifier.basicMarquee(), - ) - if (!TextUtils.isEmpty(state.secondaryLabel)) { - Text( - state.secondaryLabel.toString(), - color = colorAttr(state.colors.secondaryLabel), - modifier = Modifier.basicMarquee(), - ) - } + Box( + modifier = + Modifier.clickable(enabled = canClick) { onClick.invoke(viewModel.tileSpec) } + .animateItem() + .semantics { + onClick(onClickActionName) { false } + this.stateDescription = stateDescription + } + ) { + EditTile( + tileViewModel = viewModel, + isIconOnly(viewModel.tileSpec), + modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height)) + ) + if (canClick) { + Badge(clickAction, Modifier.align(Alignment.TopEnd)) } } } } +@Composable +fun Badge(action: ClickAction, modifier: Modifier = Modifier) { + Box(modifier = modifier.size(16.dp).background(Color.Cyan, shape = CircleShape)) { + Icon( + imageVector = + when (action) { + ClickAction.ADD -> Icons.Filled.Add + ClickAction.REMOVE -> Icons.Filled.Remove + }, + "", + tint = Color.Black, + ) + } +} + +@Composable +fun EditTile( + tileViewModel: EditTileViewModel, + iconOnly: Boolean, + modifier: Modifier = Modifier, +) { + val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec + val colors = ActiveTileColorAttributes + + Row( + modifier = modifier.tileModifier(colors).semantics { this.contentDescription = label }, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = tileHorizontalArrangement(iconOnly) + ) { + TileContent( + label = label, + secondaryLabel = tileViewModel.appName?.load(), + colors = colors, + icon = tileViewModel.icon, + iconOnly = iconOnly, + animateIconToEnd = true, + ) + } +} + +enum class ClickAction { + ADD, + REMOVE, +} + @OptIn(ExperimentalAnimationGraphicsApi::class) @Composable -private fun TileIcon(icon: IconModel, color: Color) { +private fun TileIcon( + icon: Icon, + color: Color, + animateToEnd: Boolean = false, +) { val modifier = Modifier.size(dimensionResource(id = R.dimen.qs_icon_size)) val context = LocalContext.current val loadedDrawable = remember(icon, context) { when (icon) { - is IconModel.Loaded -> icon.drawable - is IconModel.Resource -> AppCompatResources.getDrawable(context, icon.res) + is Icon.Loaded -> icon.drawable + is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res) } } if (loadedDrawable !is Animatable) { @@ -137,14 +329,19 @@ private fun TileIcon(icon: IconModel, color: Color) { tint = color, modifier = modifier, ) - } else if (icon is IconModel.Resource) { + } else if (icon is Icon.Resource) { val image = AnimatedImageVector.animatedVectorResource(id = icon.res) - var atEnd by remember(icon.res) { mutableStateOf(false) } - LaunchedEffect(key1 = icon.res) { - delay(350) - atEnd = true - } - val painter = rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) + val painter = + if (animateToEnd) { + rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) + } else { + var atEnd by remember(icon.res) { mutableStateOf(false) } + LaunchedEffect(key1 = icon.res) { + delay(350) + atEnd = true + } + rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) + } Image( painter = painter, contentDescription = null, @@ -153,3 +350,54 @@ private fun TileIcon(icon: IconModel, color: Color) { ) } } + +@Composable +private fun Modifier.tileModifier(colors: TileColorAttributes): Modifier { + return fillMaxWidth() + .clip(RoundedCornerShape(dimensionResource(R.dimen.qs_corner_radius))) + .background(colorAttr(colors.background)) + .padding(horizontal = dimensionResource(id = R.dimen.qs_label_container_margin)) +} + +@Composable +private fun tileHorizontalArrangement(iconOnly: Boolean): Arrangement.Horizontal { + val horizontalAlignment = + if (iconOnly) { + Alignment.CenterHorizontally + } else { + Alignment.Start + } + return spacedBy( + space = dimensionResource(id = R.dimen.qs_label_container_margin), + alignment = horizontalAlignment + ) +} + +@Composable +private fun TileContent( + label: String, + secondaryLabel: String?, + icon: Icon, + colors: TileColorAttributes, + iconOnly: Boolean, + animateIconToEnd: Boolean = false, +) { + TileIcon(icon, colorAttr(colors.icon), animateIconToEnd) + + if (!iconOnly) { + Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) { + Text( + label, + color = colorAttr(colors.label), + modifier = Modifier.basicMarquee(), + ) + if (!TextUtils.isEmpty(secondaryLabel)) { + Text( + secondaryLabel ?: "", + color = colorAttr(colors.secondaryLabel), + modifier = Modifier.basicMarquee(), + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt index a528eed4a346..2dab7c3d61ca 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileGrid.kt @@ -16,21 +16,16 @@ package com.android.systemui.qs.panels.ui.compose -import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.res.dimensionResource +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel -import com.android.systemui.res.R @Composable fun TileGrid(viewModel: TileGridViewModel, modifier: Modifier = Modifier) { - val gridLayout by viewModel.gridLayout.collectAsState(InfiniteGridLayout()) - val tiles by viewModel.tileViewModels.collectAsState(emptyList()) + val gridLayout by viewModel.gridLayout.collectAsStateWithLifecycle() + val tiles by viewModel.tileViewModels.collectAsStateWithLifecycle(emptyList()) - gridLayout.TileGrid(tiles, modifier) { - Tile(it, modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))) - } + gridLayout.TileGrid(tiles, modifier) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt new file mode 100644 index 000000000000..69f50a7986d5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.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.qs.panels.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.panels.domain.interactor.EditTilesListInteractor +import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor +import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.qs.panels.ui.compose.GridLayout +import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor +import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END +import com.android.systemui.qs.pipeline.domain.interactor.MinimumTilesInteractor +import com.android.systemui.qs.pipeline.shared.TileSpec +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +@SysUISingleton +@OptIn(ExperimentalCoroutinesApi::class) +class EditModeViewModel +@Inject +constructor( + private val editTilesListInteractor: EditTilesListInteractor, + private val currentTilesInteractor: CurrentTilesInteractor, + private val minTilesInteractor: MinimumTilesInteractor, + private val defaultGridLayout: InfiniteGridLayout, + @Application private val applicationScope: CoroutineScope, + gridLayoutTypeInteractor: GridLayoutTypeInteractor, + gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>, +) { + private val _isEditing = MutableStateFlow(false) + + /** + * Whether we should be editing right now. Use [startEditing] and [stopEditing] to change this + */ + val isEditing = _isEditing.asStateFlow() + private val minimumTiles: Int + get() = minTilesInteractor.minNumberOfTiles + + val gridLayout: StateFlow<GridLayout> = + gridLayoutTypeInteractor.layout + .map { gridLayoutMap[it] ?: defaultGridLayout } + .stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + defaultGridLayout, + ) + + /** + * Flow of view models for each tile that should be visible in edit mode (or empty flow when not + * editing). + * + * Guarantees of the data: + * * The data for the tiles is fetched once whenever [isEditing] goes from `false` to `true`. + * This prevents icons/labels changing while in edit mode. + * * It tracks the current tiles as they are added/removed/moved by the user. + * * The tiles that are current will be in the same relative order as the user sees them in + * Quick Settings. + * * The tiles that are not current will preserve their relative order even when the current + * tiles change. + */ + val tiles = + isEditing.flatMapLatest { + if (it) { + val editTilesData = editTilesListInteractor.getTilesToEdit() + currentTilesInteractor.currentTiles.map { tiles -> + val currentSpecs = tiles.map { it.spec } + val canRemoveTiles = currentSpecs.size > minimumTiles + val allTiles = editTilesData.stockTiles + editTilesData.customTiles + val allTilesMap = allTiles.associate { it.tileSpec to it } + val currentTiles = currentSpecs.map { allTilesMap.get(it) }.filterNotNull() + val nonCurrentTiles = allTiles.filter { it.tileSpec !in currentSpecs } + + (currentTiles + nonCurrentTiles).map { + val current = it.tileSpec in currentSpecs + val availableActions = buildSet { + if (current) { + add(AvailableEditActions.MOVE) + if (canRemoveTiles) { + add(AvailableEditActions.REMOVE) + } + } else { + add(AvailableEditActions.ADD) + } + } + EditTileViewModel( + it.tileSpec, + it.icon, + it.label, + it.appName, + current, + availableActions + ) + } + } + } else { + emptyFlow() + } + } + + /** @see isEditing */ + fun startEditing() { + _isEditing.value = true + } + + /** @see isEditing */ + fun stopEditing() { + _isEditing.value = false + } + + /** Immediately moves [tileSpec] to [position]. */ + fun moveTile(tileSpec: TileSpec, position: Int) { + throw NotImplementedError("This is not supported yet") + } + + /** Immediately adds [tileSpec] to the current tiles at [position]. */ + fun addTile(tileSpec: TileSpec, position: Int = POSITION_AT_END) { + currentTilesInteractor.addTile(tileSpec, position) + } + + /** Immediately removes [tileSpec] from the current tiles. */ + fun removeTile(tileSpec: TileSpec) { + currentTilesInteractor.removeTiles(listOf(tileSpec)) + } + + /** Immediately resets the current tiles to the default list. */ + fun resetCurrentTilesToDefault() { + throw NotImplementedError("This is not supported yet") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt new file mode 100644 index 000000000000..ba9a0442503d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.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.qs.panels.ui.viewmodel + +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.common.shared.model.Text +import com.android.systemui.qs.pipeline.shared.TileSpec + +/** + * View model for each tile that is available to be added/removed/moved in Edit mode. + * + * [isCurrent] indicates whether this tile is part of the current set of tiles that the user sees in + * Quick Settings. + */ +class EditTileViewModel( + val tileSpec: TileSpec, + val icon: Icon, + val label: Text, + val appName: Text?, + val isCurrent: Boolean, + val availableEditActions: Set<AvailableEditActions>, +) + +enum class AvailableEditActions { + ADD, + REMOVE, + MOVE, +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt index fc134602e9cb..5eee691dcf79 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModel.kt @@ -17,34 +17,39 @@ package com.android.systemui.qs.panels.ui.viewmodel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.qs.panels.domain.interactor.GridLayoutTypeInteractor -import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor import com.android.systemui.qs.panels.shared.model.GridLayoutType import com.android.systemui.qs.panels.ui.compose.GridLayout import com.android.systemui.qs.panels.ui.compose.InfiniteGridLayout import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class TileGridViewModel @Inject constructor( gridLayoutTypeInteractor: GridLayoutTypeInteractor, - gridLayoutMap: Map<Class<out GridLayoutType>, @JvmSuppressWildcards GridLayout>, + gridLayoutMap: Map<GridLayoutType, @JvmSuppressWildcards GridLayout>, tilesInteractor: CurrentTilesInteractor, - iconTilesInteractor: IconTilesInteractor, + defaultGridLayout: InfiniteGridLayout, + @Application private val applicationScope: CoroutineScope ) { - val gridLayout: Flow<GridLayout> = - gridLayoutTypeInteractor.layout.map { - gridLayoutMap[it::class.java] ?: InfiniteGridLayout() - } + val gridLayout: StateFlow<GridLayout> = + gridLayoutTypeInteractor.layout + .map { gridLayoutMap[it] ?: defaultGridLayout } + .stateIn(applicationScope, SharingStarted.Eagerly, defaultGridLayout) val tileViewModels: Flow<List<TileViewModel>> = - combine(tilesInteractor.currentTiles, iconTilesInteractor.iconTilesSpecs) { - tiles, - iconTilesSpecs -> - tiles.map { TileViewModel(it.tile, iconTilesSpecs.contains(it.spec)) } + tilesInteractor.currentTiles.mapLatest { tiles -> + tiles.map { TileViewModel(it.tile, it.spec) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt index f4b7255693e2..58d07c368456 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt @@ -24,15 +24,13 @@ data class TileUiState( val secondaryLabel: CharSequence, val colors: TileColorAttributes, val icon: Supplier<QSTile.Icon>, - val iconOnly: Boolean, ) -fun QSTile.State.toUiState(iconOnly: Boolean): TileUiState { +fun QSTile.State.toUiState(): TileUiState { return TileUiState( label ?: "", secondaryLabel ?: "", colors(), icon?.let { Supplier { icon } } ?: iconSupplier, - iconOnly, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt index 08e9119a6c76..a6cfa75a7583 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileViewModel.kt @@ -16,19 +16,17 @@ package com.android.systemui.qs.panels.ui.viewmodel -import android.view.View -import android.view.View.OnLongClickListener +import com.android.systemui.animation.Expandable import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -class TileViewModel(private val tile: QSTile, val iconOnly: Boolean = false) : - OnLongClickListener, View.OnClickListener { - val state: Flow<TileUiState> = +class TileViewModel(private val tile: QSTile, val spec: TileSpec) { + val state: Flow<QSTile.State> = conflatedCallbackFlow { val callback = QSTile.Callback { trySend(it.copy()) } @@ -37,19 +35,17 @@ class TileViewModel(private val tile: QSTile, val iconOnly: Boolean = false) : awaitClose { tile.removeCallback(callback) } } .onStart { emit(tile.state) } - .map { it.toUiState(iconOnly) } .distinctUntilChanged() - val currentState: TileUiState - get() = tile.state.toUiState(iconOnly) + val currentState: QSTile.State + get() = tile.state - override fun onClick(view: View?) { - tile.click(view) + fun onClick(expandable: Expandable?) { + tile.click(expandable) } - override fun onLongClick(view: View?): Boolean { - tile.longClick(view) - return true + fun onLongClick(expandable: Expandable?) { + tile.longClick(expandable) } fun startListening(token: Any) = tile.setListening(token, true) diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt index cfcea9829610..c5b27376a82a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt @@ -23,6 +23,7 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.content.pm.PackageManager.ResolveInfoFlags +import android.content.pm.ServiceInfo import android.os.UserHandle import android.service.quicksettings.TileService import androidx.annotation.GuardedBy @@ -36,14 +37,17 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn interface InstalledTilesComponentRepository { fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> + + fun getInstalledTilesServiceInfos(userId: Int): List<ServiceInfo> } @SysUISingleton @@ -55,38 +59,45 @@ constructor( private val packageChangeRepository: PackageChangeRepository ) : InstalledTilesComponentRepository { - @GuardedBy("userMap") private val userMap = mutableMapOf<Int, Flow<Set<ComponentName>>>() + @GuardedBy("userMap") private val userMap = mutableMapOf<Int, StateFlow<List<ServiceInfo>>>() override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> = - synchronized(userMap) { - userMap.getOrPut(userId) { - /* - * In order to query [PackageManager] for different users, this implementation will - * call [Context.createContextAsUser] and retrieve the [PackageManager] from that - * context. - */ - val packageManager = - if (applicationContext.userId == userId) { - applicationContext.packageManager - } else { - applicationContext - .createContextAsUser( - UserHandle.of(userId), - /* flags */ 0, - ) - .packageManager - } - packageChangeRepository - .packageChanged(UserHandle.of(userId)) - .onStart { emit(PackageChangeModel.Empty) } - .map { reloadComponents(userId, packageManager) } - .distinctUntilChanged() - .shareIn(backgroundScope, SharingStarted.WhileSubscribed(), replay = 1) - } + synchronized(userMap) { getForUserLocked(userId) } + .map { it.mapTo(mutableSetOf()) { it.componentName } } + + override fun getInstalledTilesServiceInfos(userId: Int): List<ServiceInfo> { + return synchronized(userMap) { getForUserLocked(userId).value } + } + + private fun getForUserLocked(userId: Int): StateFlow<List<ServiceInfo>> { + return userMap.getOrPut(userId) { + /* + * In order to query [PackageManager] for different users, this implementation will + * call [Context.createContextAsUser] and retrieve the [PackageManager] from that + * context. + */ + val packageManager = + if (applicationContext.userId == userId) { + applicationContext.packageManager + } else { + applicationContext + .createContextAsUser( + UserHandle.of(userId), + /* flags */ 0, + ) + .packageManager + } + packageChangeRepository + .packageChanged(UserHandle.of(userId)) + .onStart { emit(PackageChangeModel.Empty) } + .map { reloadComponents(userId, packageManager) } + .distinctUntilChanged() + .stateIn(backgroundScope, SharingStarted.WhileSubscribed(), emptyList()) } + } @WorkerThread - private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> { + private fun reloadComponents(userId: Int, packageManager: PackageManager): List<ServiceInfo> { return packageManager .queryIntentServicesAsUser(INTENT, FLAGS, userId) .mapNotNull { it.serviceInfo } @@ -100,7 +111,6 @@ constructor( false } } - .mapTo(mutableSetOf()) { it.componentName } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt index 61896f0a3816..b7fcef4376ea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -115,6 +115,10 @@ interface CurrentTilesInteractor : ProtoDumpable { * @see TileSpecRepository.setTiles */ fun setTiles(specs: List<TileSpec>) + + companion object { + val POSITION_AT_END: Int = TileSpecRepository.POSITION_AT_END + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractor.kt index 4e40042a49b0..2ae3f07d6b67 100644 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractor.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. @@ -13,22 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.systemui.contrast -import android.app.Activity -import android.os.Bundle +package com.android.systemui.qs.pipeline.domain.interactor + +import com.android.systemui.qs.pipeline.data.repository.MinimumTilesRepository import javax.inject.Inject -/** Trampoline activity responsible for creating a [ContrastDialogDelegate] */ -class ContrastDialogActivity +class MinimumTilesInteractor @Inject constructor( - private val contrastDialogDelegate : ContrastDialogDelegate -) : Activity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - contrastDialogDelegate.createDialog().show() - finish() - } + private val minimumTilesRepository: MinimumTilesRepository, +) { + val minNumberOfTiles: Int + get() = minimumTilesRepository.minNumberOfTiles } 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 145674747bb6..56588ff75a5a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -42,7 +42,6 @@ import android.text.format.DateUtils; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; -import android.view.View; import androidx.annotation.Nullable; import androidx.lifecycle.Lifecycle; @@ -56,8 +55,10 @@ 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; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; @@ -137,9 +138,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy * * Calls to the controller should be made here to set the new state of the device. * - * @param view The view that was clicked. + * @param expandable {@link Expandable} that was clicked. */ - protected abstract void handleClick(@Nullable View view); + protected abstract void handleClick(@Nullable Expandable expandable); /** * Update state of the tile based on device state @@ -282,7 +283,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mHandler.sendEmptyMessage(H.REMOVE_CALLBACKS); } - public void click(@Nullable View view) { + @Override + public void click(@Nullable Expandable expandable) { mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION) .addTaggedData(FIELD_STATUS_BAR_STATE, mStatusBarStateController.getState()))); @@ -292,11 +294,12 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state, eventId); if (!mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { - mHandler.obtainMessage(H.CLICK, eventId, 0, view).sendToTarget(); + mHandler.obtainMessage(H.CLICK, eventId, 0, expandable).sendToTarget(); } } - public void secondaryClick(@Nullable View view) { + @Override + public void secondaryClick(@Nullable Expandable expandable) { mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION) .addTaggedData(FIELD_STATUS_BAR_STATE, mStatusBarStateController.getState()))); @@ -305,11 +308,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy final int eventId = mClickEventId++; mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(), mState.state, eventId); - mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, view).sendToTarget(); + mHandler.obtainMessage(H.SECONDARY_CLICK, eventId, 0, expandable).sendToTarget(); } @Override - public void longClick(@Nullable View view) { + public void longClick(@Nullable Expandable expandable) { mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION) .addTaggedData(FIELD_STATUS_BAR_STATE, mStatusBarStateController.getState()))); @@ -319,7 +322,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state, eventId); if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { - mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget(); + mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, expandable).sendToTarget(); } } @@ -397,22 +400,22 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy * * Defaults to {@link QSTileImpl#handleClick} * - * @param view The view that was clicked. + * @param expandable {@link Expandable} that was clicked. */ - protected void handleSecondaryClick(@Nullable View view) { + protected void handleSecondaryClick(@Nullable Expandable expandable) { // Default to normal click. - handleClick(view); + handleClick(expandable); } /** * Handles long click on the tile by launching the {@link Intent} defined in * {@link QSTileImpl#getLongClickIntent}. * - * @param view The view from which the opening window will be animated. + * @param expandable {@link Expandable} from which the opening window will be animated. */ - protected void handleLongClick(@Nullable View view) { + protected void handleLongClick(@Nullable Expandable expandable) { ActivityTransitionAnimator.Controller animationController = - view != null ? ActivityTransitionAnimator.Controller.fromView(view, + expandable != null ? expandable.activityTransitionController( InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) : null; mActivityStarter.postStartActivityDismissingKeyguard(getLongClickIntent(), 0, animationController); @@ -591,16 +594,16 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mActivityStarter.postStartActivityDismissingKeyguard(intent, 0); } else { mQSLogger.logHandleClick(mTileSpec, msg.arg1); - handleClick((View) msg.obj); + handleClick((Expandable) msg.obj); } } else if (msg.what == SECONDARY_CLICK) { name = "handleSecondaryClick"; mQSLogger.logHandleSecondaryClick(mTileSpec, msg.arg1); - handleSecondaryClick((View) msg.obj); + handleSecondaryClick((Expandable) msg.obj); } else if (msg.what == LONG_CLICK) { name = "handleLongClick"; mQSLogger.logHandleLongClick(mTileSpec, msg.arg1); - handleLongClick((View) msg.obj); + handleLongClick((Expandable) msg.obj); } else if (msg.what == REFRESH_STATE) { name = "handleRefreshState"; handleRefreshState(msg.obj); @@ -630,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/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index ca5b77108505..c6dfdd5c137b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -53,6 +53,7 @@ import com.android.settingslib.Utils import com.android.systemui.Flags import com.android.systemui.Flags.quickSettingsVisualHapticsLongpress import com.android.systemui.FontSizeUtils +import com.android.systemui.animation.Expandable import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate import com.android.systemui.haptics.qs.QSLongPressEffect @@ -147,7 +148,8 @@ open class QSTileViewImpl @JvmOverloads constructor( */ protected var showRippleEffect = true - private lateinit var qsTileBackground: LayerDrawable + private lateinit var qsTileBackground: RippleDrawable + private lateinit var qsTileFocusBackground: Drawable private lateinit var backgroundDrawable: LayerDrawable private lateinit var backgroundBaseDrawable: Drawable private lateinit var backgroundOverlayDrawable: Drawable @@ -185,7 +187,8 @@ open class QSTileViewImpl @JvmOverloads constructor( private val locInScreen = IntArray(2) /** Visuo-haptic long-press effects */ - private var haveLongPressPropertiesBeenReset = true + var haveLongPressPropertiesBeenReset = true + private set private var paddingForLaunch = Rect() private var initialLongPressProperties: QSLongPressProperties? = null private var finalLongPressProperties: QSLongPressProperties? = null @@ -311,10 +314,11 @@ open class QSTileViewImpl @JvmOverloads constructor( private fun createTileBackground(): Drawable { qsTileBackground = if (Flags.qsTileFocusState()) { - mContext.getDrawable(R.drawable.qs_tile_background_flagged) as LayerDrawable + mContext.getDrawable(R.drawable.qs_tile_background_flagged) as RippleDrawable } else { mContext.getDrawable(R.drawable.qs_tile_background) as RippleDrawable } + qsTileFocusBackground = mContext.getDrawable(R.drawable.qs_tile_focused_background)!! backgroundDrawable = qsTileBackground.findDrawableByLayerId(R.id.background) as LayerDrawable backgroundBaseDrawable = @@ -330,6 +334,17 @@ open class QSTileViewImpl @JvmOverloads constructor( updateHeight() } + override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect) + if (Flags.qsTileFocusState()) { + if (gainFocus) { + qsTileFocusBackground.setBounds(0, 0, width, height) + overlay.add(qsTileFocusBackground) + } else { + overlay.clear() + } + } + } private fun updateHeight() { // TODO(b/332900989): Find a more robust way of resetting the tile if not reset by the // launch animation. @@ -364,10 +379,11 @@ open class QSTileViewImpl @JvmOverloads constructor( } override fun init(tile: QSTile) { + val expandable = Expandable.fromView(this) init( - { v: View? -> tile.click(this) }, - { view: View? -> - tile.longClick(this) + { _: View? -> tile.click(expandable) }, + { _: View? -> + tile.longClick(expandable) true } ) @@ -770,7 +786,11 @@ open class QSTileViewImpl @JvmOverloads constructor( } } - override fun onActivityLaunchAnimationEnd() = resetLongPressEffectProperties() + override fun onActivityLaunchAnimationEnd() { + if (longPressEffect != null && !haveLongPressPropertiesBeenReset) { + resetLongPressEffectProperties() + } + } fun prepareForLaunch() { val startingHeight = initialLongPressProperties?.height?.toInt() ?: 0 @@ -875,8 +895,8 @@ open class QSTileViewImpl @JvmOverloads constructor( background.updateBounds( left = 0, top = 0, - right = initialLongPressProperties?.width?.toInt() ?: 0, - bottom = initialLongPressProperties?.height?.toInt() ?: 0, + right = initialLongPressProperties?.width?.toInt() ?: measuredWidth, + bottom = initialLongPressProperties?.height?.toInt() ?: measuredHeight, ) changeCornerRadius(resources.getDimensionPixelSize(R.dimen.qs_corner_radius).toFloat()) setAllColors( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 17251c37473f..71b69c92b87d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -16,6 +16,8 @@ package com.android.systemui.qs.tiles; +import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_AIRPLANE_MODE; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -29,13 +31,16 @@ import android.provider.Settings.Global; import android.service.quicksettings.Tile; import android.sysprop.TelephonyProperties; import android.telephony.TelephonyManager; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.telephony.flags.Flags; +import com.android.settingslib.satellite.SatelliteDialogUtils; +import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -54,6 +59,8 @@ import com.android.systemui.util.settings.GlobalSettings; import dagger.Lazy; +import kotlinx.coroutines.Job; + import javax.inject.Inject; /** Quick settings tile: Airplane mode **/ @@ -66,6 +73,9 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { private final Lazy<ConnectivityManager> mLazyConnectivityManager; private boolean mListening; + @Nullable + @VisibleForTesting + Job mClickJob; @Inject public AirplaneModeTile( @@ -103,7 +113,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { } @Override - public void handleClick(@Nullable View view) { + public void handleClick(@Nullable Expandable expandable) { boolean airplaneModeEnabled = mState.value; MetricsLogger.action(mContext, getMetricsCategory(), !airplaneModeEnabled); if (!airplaneModeEnabled && TelephonyProperties.in_ecm_mode().orElse(false)) { @@ -111,6 +121,21 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { new Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), 0); return; } + + if (Flags.oemEnabledSatelliteFlag()) { + if (mClickJob != null && !mClickJob.isCompleted()) { + return; + } + mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog( + mContext, this, TYPE_IS_AIRPLANE_MODE, isAllowClick -> { + if (isAllowClick) { + setEnabled(!airplaneModeEnabled); + } + return null; + }); + return; + } + setEnabled(!airplaneModeEnabled); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index 688f3ca38087..73d991f6efe7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -9,12 +9,10 @@ import android.provider.AlarmClock import android.service.quicksettings.Tile import android.text.TextUtils import android.text.format.DateFormat -import android.view.View import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger -import com.android.systemui.res.R -import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -25,12 +23,15 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.NextAlarmController import java.util.Locale import javax.inject.Inject -class AlarmTile @Inject constructor( +class AlarmTile +@Inject +constructor( host: QSHost, uiEventLogger: QsEventLogger, @Background backgroundLooper: Looper, @@ -56,8 +57,7 @@ class AlarmTile @Inject constructor( private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null private val icon = ResourceIcon.get(R.drawable.ic_alarm) - @VisibleForTesting - internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS) + @VisibleForTesting internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS) private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm -> lastAlarmInfo = nextAlarm refreshState() @@ -73,11 +73,11 @@ class AlarmTile @Inject constructor( } } - override fun handleClick(view: View?) { - val animationController = view?.let { - ActivityTransitionAnimator.Controller.fromView( - it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) - } + override fun handleClick(expandable: Expandable?) { + val animationController = + expandable?.activityTransitionController( + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE + ) val pendingIntent = lastAlarmInfo?.showIntent if (pendingIntent != null) { mActivityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java index 426aa553f082..7c0ce4cc75a9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java @@ -21,7 +21,6 @@ import android.os.Looper; import android.provider.Settings; import android.provider.Settings.Secure; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; @@ -29,6 +28,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -121,7 +121,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements if (!listening) { // If we stopped listening, it means that the tile is not visible. In that case, we // don't need to save the view anymore - mBatteryController.clearLastPowerSaverStartView(); + mBatteryController.clearLastPowerSaverStartExpandable(); } } @@ -131,11 +131,11 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (getState().state == Tile.STATE_UNAVAILABLE) { return; } - mBatteryController.setPowerSaveMode(!mPowerSave, view); + mBatteryController.setPowerSaveMode(!mPowerSave, expandable); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 6eae32a0ffd6..9f41d98b6969 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_BLUETOOTH; import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; import android.annotation.Nullable; @@ -31,14 +32,17 @@ import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; -import android.view.View; import android.widget.Switch; +import androidx.annotation.VisibleForTesting; + import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.satellite.SatelliteDialogUtils; +import com.android.systemui.animation.Expandable; import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -55,6 +59,8 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; +import kotlinx.coroutines.Job; + import java.util.List; import java.util.concurrent.Executor; @@ -78,6 +84,9 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { private final BluetoothTileDialogViewModel mDialogViewModel; private final FeatureFlags mFeatureFlags; + @Nullable + @VisibleForTesting + Job mClickJob; @Inject public BluetoothTile( @@ -109,9 +118,27 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { + if (com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag()) { + if (mClickJob != null && !mClickJob.isCompleted()) { + return; + } + mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog( + mContext, this, TYPE_IS_BLUETOOTH, isAllowClick -> { + if (!isAllowClick) { + return null; + } + handleClickEvent(expandable); + return null; + }); + return; + } + handleClickEvent(expandable); + } + + private void handleClickEvent(@Nullable Expandable expandable) { if (mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG)) { - mDialogViewModel.showDialog(view); + mDialogViewModel.showDialog(expandable); } else { // Secondary clicks are header clicks, just toggle. final boolean isEnabled = mState.value; @@ -127,7 +154,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { } @Override - protected void handleSecondaryClick(@Nullable View view) { + protected void handleSecondaryClick(@Nullable Expandable expandable) { if (!mController.canConfigBluetooth()) { mActivityStarter.postStartActivityDismissingKeyguard( new Intent(Settings.ACTION_BLUETOOTH_SETTINGS), 0); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index b27b974dc972..169cdc17c2c0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -29,7 +29,6 @@ import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; import android.util.Log; -import android.view.View; import android.widget.Button; import androidx.annotation.Nullable; @@ -41,6 +40,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -161,12 +161,12 @@ public class CastTile extends QSTileImpl<BooleanState> { } @Override - protected void handleLongClick(@Nullable View view) { - handleClick(view); + protected void handleLongClick(@Nullable Expandable expandable) { + handleClick(expandable); } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (getState().state == Tile.STATE_UNAVAILABLE) { return; } @@ -174,7 +174,7 @@ public class CastTile extends QSTileImpl<BooleanState> { List<CastDevice> activeDevices = getActiveDevices(); if (willPopDialog()) { if (!mKeyguard.isShowing()) { - showDialog(view); + showDialog(expandable); } else { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { // Dismissing the keyguard will collapse the shade, so we don't animate from the @@ -216,7 +216,7 @@ public class CastTile extends QSTileImpl<BooleanState> { } } - private void showDialog(@Nullable View view) { + private void showDialog(@Nullable Expandable expandable) { mUiHandler.post(() -> { final DialogHolder holder = new DialogHolder(); final Dialog dialog = MediaRouteDialogPresenter.createDialog( @@ -241,17 +241,21 @@ public class CastTile extends QSTileImpl<BooleanState> { SystemUIDialog.setDialogSize(dialog); mUiHandler.post(() -> { - if (view != null) { - mDialogTransitionAnimator.showFromView(dialog, view, - new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG)); - } else { - if (dialog.getWindow() != null) { - DialogKt.registerAnimationOnBackInvoked(dialog, - dialog.getWindow().getDecorView()); + if (expandable != null) { + DialogTransitionAnimator.Controller controller = + expandable.dialogTransitionController( + new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG)); + if (controller != null) { + mDialogTransitionAnimator.show(dialog, controller); + return; } - dialog.show(); } + if (dialog.getWindow() != null) { + DialogKt.registerAnimationOnBackInvoked(dialog, + dialog.getWindow().getDecorView()); + } + dialog.show(); }); }); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java index c8adbfcf5487..871973dfcb7f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java @@ -22,12 +22,12 @@ import android.os.Looper; import android.provider.Settings; import android.provider.Settings.Secure; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -109,7 +109,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { mSetting.setValue(mState.value ? 0 : 1); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index c34a5842c1e3..58969107ad22 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -22,13 +22,13 @@ import android.os.Looper; import android.provider.Settings; import android.provider.Settings.Secure; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -108,7 +108,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { mSetting.setValue(mState.value ? 0 : 1); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index 58630a0b6b99..7760943476bf 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -19,7 +19,6 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; @@ -30,6 +29,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Prefs; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -47,7 +47,7 @@ import com.android.systemui.statusbar.policy.DataSaverController; import javax.inject.Inject; public class DataSaverTile extends QSTileImpl<BooleanState> implements - DataSaverController.Listener{ + DataSaverController.Listener { public static final String TILE_SPEC = "saver"; @@ -89,8 +89,9 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements public Intent getLongClickIntent() { return new Intent(Settings.ACTION_DATA_SAVER_SETTINGS); } + @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (mState.value || Prefs.getBoolean(mContext, Prefs.Key.QS_DATA_SAVER_DIALOG_SHOWN, false)) { // Do it right away. @@ -112,10 +113,16 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements dialog.setNeutralButton(com.android.internal.R.string.cancel, null); dialog.setShowForAllUsers(true); - if (view != null) { - mDialogTransitionAnimator.showFromView(dialog, view, new DialogCuj( - InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG)); + if (expandable != null) { + DialogTransitionAnimator.Controller controller = + expandable.dialogTransitionController(new DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG)); + if (controller != null) { + mDialogTransitionAnimator.show(dialog, controller); + } else { + dialog.show(); + } } else { dialog.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index bb175e2a8534..cc8a73423174 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -1,3 +1,4 @@ + /* * Copyright (C) 2021 The Android Open Source Project * @@ -21,12 +22,11 @@ import android.content.Intent import android.os.Handler import android.os.Looper import android.service.quicksettings.Tile -import android.view.View import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R -import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE @@ -100,26 +100,30 @@ class DeviceControlsTile @Inject constructor( } } - override fun handleClick(view: View?) { + override fun handleClick(expandable: Expandable?) { if (state.state == Tile.STATE_UNAVAILABLE) { return } val intent = Intent().apply { component = ComponentName(mContext, controlsComponent.getControlsUiController().get() - .resolveActivity()) + .resolveActivity()) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) putExtra(ControlsUiController.EXTRA_ANIMATE, true) } - val animationController = view?.let { - ActivityTransitionAnimator.Controller.fromView( - it, InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE) - } + val animationController = + expandable?.activityTransitionController( + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE + ) mUiHandler.post { val showOverLockscreenWhenLocked = state.state == Tile.STATE_ACTIVE mActivityStarter.startActivity( - intent, true /* dismissShade */, animationController, showOverLockscreenWhenLocked) + intent, + true /* dismissShade */, + animationController, + showOverLockscreenWhenLocked, + ) } } @@ -130,7 +134,7 @@ class DeviceControlsTile @Inject constructor( if (controlsComponent.isEnabled() && hasControlsApps.get()) { if (controlsComponent.getVisibility() == AVAILABLE) { val selection = controlsComponent - .getControlsController().get().getPreferredSelection() + .getControlsController().get().getPreferredSelection() state.state = if (selection is SelectedItem.StructureItem && selection.structure.controls.isEmpty()) { Tile.STATE_INACTIVE @@ -157,7 +161,7 @@ class DeviceControlsTile @Inject constructor( return null } - override fun handleLongClick(view: View?) {} + override fun handleLongClick(expandable: Expandable?) {} override fun getTileLabel(): CharSequence { return mContext.getText(controlsComponent.getTileTitleId()) @@ -166,4 +170,4 @@ class DeviceControlsTile @Inject constructor( companion object { const val TILE_SPEC = "controls" } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index f62b60bd887f..4ebebeade1f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -35,7 +35,6 @@ import android.service.notification.ZenModeConfig; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; @@ -47,6 +46,7 @@ import com.android.settingslib.notification.EnableZenModeDialog; import com.android.systemui.Prefs; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -147,12 +147,12 @@ public class DndTile extends QSTileImpl<BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { // Zen is currently on if (mState.value) { mController.setZen(ZEN_MODE_OFF, null, TAG); } else { - enableZenMode(view); + enableZenMode(expandable); } } @@ -162,7 +162,7 @@ public class DndTile extends QSTileImpl<BooleanState> { mSettingZenDuration.setUserId(newUserId); } - private void enableZenMode(@Nullable View view) { + private void enableZenMode(@Nullable Expandable expandable) { int zenDuration = mSettingZenDuration.getValue(); boolean showOnboarding = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 0) != 0 @@ -183,11 +183,17 @@ public class DndTile extends QSTileImpl<BooleanState> { case Settings.Secure.ZEN_DURATION_PROMPT: mUiHandler.post(() -> { Dialog dialog = makeZenModeDialog(); - if (view != null) { - mDialogTransitionAnimator.showFromView(dialog, view, new DialogCuj( + if (expandable != null) { + DialogTransitionAnimator.Controller controller = + expandable.dialogTransitionController(new DialogCuj( InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG), - /* animateBackgroundBoundsChange= */ false); + INTERACTION_JANK_TAG)); + if (controller != null) { + mDialogTransitionAnimator.show(dialog, + controller, /* animateBackgroundBoundsChange= */ false); + } else { + dialog.show(); + } } else { dialog.show(); } @@ -217,8 +223,8 @@ public class DndTile extends QSTileImpl<BooleanState> { } @Override - protected void handleSecondaryClick(@Nullable View view) { - handleLongClick(view); + protected void handleSecondaryClick(@Nullable Expandable expandable) { + handleLongClick(expandable); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java index 4f0a63b667f3..0d3d980f71f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java @@ -32,12 +32,12 @@ import android.service.dreams.IDreamManager; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; -import android.view.View; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -153,7 +153,7 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { try { if (mDreamManager.isDreaming()) { mDreamManager.awaken(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index f022981bd8db..848ff3c533ba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -22,13 +22,13 @@ import android.os.Handler; import android.os.Looper; import android.provider.MediaStore; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -99,7 +99,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (ActivityManager.isUserAMonkey()) { return; } @@ -114,8 +114,8 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements } @Override - protected void handleLongClick(@Nullable View view) { - handleClick(view); + protected void handleLongClick(@Nullable Expandable expandable) { + handleClick(expandable); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt index f5018a2868c0..078698c2872d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt @@ -19,12 +19,12 @@ import android.content.Intent import android.os.Handler import android.os.Looper import android.provider.Settings -import android.view.View import com.android.internal.jank.InteractionJankMonitor import com.android.internal.logging.MetricsLogger import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -74,18 +74,23 @@ constructor( return QSTile.State() } - override fun handleClick(view: View?) { + override fun handleClick(expandable: Expandable?) { // We animate from the touched view only if we are not on the keyguard - val animateFromView: Boolean = view != null && !keyguardStateController.isShowing + val animateFromExpandable: Boolean = + expandable != null && !keyguardStateController.isShowing val runnable = Runnable { val dialog: SystemUIDialog = fontScalingDialogDelegateProvider.get().createDialog() - if (animateFromView) { - dialogTransitionAnimator.showFromView( - dialog, - view!!, - DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG) - ) + if (animateFromExpandable) { + val controller = + expandable?.dialogTransitionController( + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG + ) + ) + controller?.let { dialogTransitionAnimator.show(dialog, controller) } + ?: dialog.show() } else { dialog.show() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java index 81a20260792a..183c1a4a7ce7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HearingDevicesTile.java @@ -20,13 +20,13 @@ import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.provider.Settings; -import android.view.View; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.systemui.Flags; import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -72,8 +72,8 @@ public class HearingDevicesTile extends QSTileImpl<State> { } @Override - protected void handleClick(@Nullable View view) { - mUiHandler.post(() -> mDialogManager.showDialog(view)); + protected void handleClick(@Nullable Expandable expandable) { + mUiHandler.post(() -> mDialogManager.showDialog(expandable)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 4d0404dd007e..ea3993ea88a9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -25,7 +25,6 @@ import android.os.UserManager; import android.provider.Settings; import android.service.quicksettings.Tile; import android.util.Log; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; @@ -33,6 +32,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -112,7 +112,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { final boolean isEnabled = mState.value; if (!isEnabled && mDataSaverController.isDataSaverEnabled()) { return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 0f260e3dc296..6d98da4bac61 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -30,7 +30,6 @@ import android.service.quicksettings.Tile; import android.text.Html; import android.text.TextUtils; import android.util.Log; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; @@ -41,6 +40,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settingslib.graph.SignalDrawable; import com.android.settingslib.mobile.TelephonyIcons; import com.android.settingslib.net.DataUsageController; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -124,10 +124,10 @@ public class InternetTile extends QSTileImpl<QSTile.BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { mHandler.post(() -> mInternetDialogManager.create(true, mAccessPointController.canConfigMobileData(), - mAccessPointController.canConfigWifi(), view)); + mAccessPointController.canConfigWifi(), expandable)); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt index 357743bc2bd7..932dec5af950 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTileNewImpl.kt @@ -20,9 +20,9 @@ import android.content.Intent import android.os.Handler import android.os.Looper import android.provider.Settings -import android.view.View import android.widget.Switch import com.android.internal.logging.MetricsLogger +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -44,18 +44,18 @@ import javax.inject.Inject class InternetTileNewImpl @Inject constructor( - host: QSHost, - uiEventLogger: QsEventLogger, - @Background backgroundLooper: Looper, - @Main private val mainHandler: Handler, - falsingManager: FalsingManager, - metricsLogger: MetricsLogger, - statusBarStateController: StatusBarStateController, - activityStarter: ActivityStarter, - qsLogger: QSLogger, - viewModel: InternetTileViewModel, - private val internetDialogManager: InternetDialogManager, - private val accessPointController: AccessPointController, + host: QSHost, + uiEventLogger: QsEventLogger, + @Background backgroundLooper: Looper, + @Main private val mainHandler: Handler, + falsingManager: FalsingManager, + metricsLogger: MetricsLogger, + statusBarStateController: StatusBarStateController, + activityStarter: ActivityStarter, + qsLogger: QSLogger, + viewModel: InternetTileViewModel, + private val internetDialogManager: InternetDialogManager, + private val accessPointController: AccessPointController, ) : QSTileImpl<QSTile.BooleanState>( host, @@ -84,13 +84,13 @@ constructor( return QSTile.BooleanState().also { it.forceExpandIcon = true } } - override fun handleClick(view: View?) { + override fun handleClick(expandable: Expandable?) { mainHandler.post { internetDialogManager.create( aboveStatusBar = true, accessPointController.canConfigMobileData(), accessPointController.canConfigWifi(), - view, + expandable, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index b3f0d8bfbba8..cad5c0d12d1d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -22,13 +22,13 @@ import android.os.Looper; import android.os.UserManager; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -92,7 +92,7 @@ public class LocationTile extends QSTileImpl<BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { final boolean wasEnabled = mState.value; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java index d650f73bc92b..136eea8331df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java @@ -27,13 +27,13 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -119,7 +119,7 @@ public class NfcTile extends QSTileImpl<BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (getAdapter() == null) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index a1ea46d9d69b..ac762de6d544 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -28,7 +28,6 @@ import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; @@ -36,6 +35,7 @@ import androidx.annotation.StringRes; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.NightDisplayListenerModule; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -112,7 +112,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { // Enroll in forced auto mode if eligible. if ("1".equals(Settings.Global.getString(mContext.getContentResolver(), Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE)) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java index b08e6a5580a2..450c95411c3f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java @@ -21,13 +21,13 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -114,7 +114,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { mSetting.setValue(mState.value ? 0 : 1); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java index de9a08e1cda8..9766fac7965e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java @@ -21,13 +21,13 @@ import android.os.Handler; import android.os.Looper; import android.service.quicksettings.Tile; import android.util.Log; -import android.view.View; import androidx.annotation.Nullable; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.systemui.animation.ActivityTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -99,7 +99,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { Intent intent = mQRCodeScannerController.getIntent(); if (intent == null) { // This should never happen as the fact that we are handling clicks means that the @@ -109,7 +109,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { } ActivityTransitionAnimator.Controller animationController = - view == null ? null : ActivityTransitionAnimator.Controller.fromView(view, + expandable == null ? null : expandable.activityTransitionController( InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); mActivityStarter.startActivity(intent, true /* dismissShade */, animationController, true /* showOverLockscreenWhenLocked */); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index e1b742e1e7f0..76aa146d0531 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -35,7 +35,6 @@ import android.service.quickaccesswallet.QuickAccessWalletClient; import android.service.quickaccesswallet.WalletCard; import android.service.quicksettings.Tile; import android.util.Log; -import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -44,6 +43,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.systemui.animation.ActivityTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -131,9 +131,9 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { ActivityTransitionAnimator.Controller animationController = - view == null ? null : ActivityTransitionAnimator.Controller.fromView(view, + expandable == null ? null : expandable.activityTransitionController( InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE); mUiHandler.post( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index b418a174d84e..9937ea468fa8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -24,7 +24,6 @@ import android.os.Handler import android.os.Looper import android.service.quicksettings.Tile import android.text.TextUtils -import android.view.View import android.widget.Switch import androidx.annotation.VisibleForTesting import com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN @@ -32,6 +31,7 @@ import com.android.internal.logging.MetricsLogger import com.android.systemui.Flags.recordIssueQsTile import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter @@ -113,11 +113,11 @@ constructor( } @VisibleForTesting - public override fun handleClick(view: View?) { + public override fun handleClick(expandable: Expandable?) { if (issueRecordingState.isRecording) { stopIssueRecordingService() } else { - mUiHandler.post { showPrompt(view) } + mUiHandler.post { showPrompt(expandable) } } } @@ -143,7 +143,7 @@ constructor( ) .send(BroadcastOptions.makeBasic().apply { isInteractive = true }.toBundle()) - private fun showPrompt(view: View?) { + private fun showPrompt(expandable: Expandable?) { val dialog: AlertDialog = delegateFactory .create { @@ -156,12 +156,11 @@ constructor( ActivityStarter.OnDismissAction { // We animate from the touched view only if we are not on the keyguard, given // that if we are we will dismiss it which will also collapse the shade. - if (view != null && !keyguardStateController.isShowing) { - dialogTransitionAnimator.showFromView( - dialog, - view, - DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC) - ) + if (expandable != null && !keyguardStateController.isShowing) { + expandable + .dialogTransitionController(DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC)) + ?.let { dialogTransitionAnimator.show(dialog, it) } + ?: dialog.show() } else { dialog.show() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index 76ada102f26a..34723523b84f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -23,13 +23,13 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.R; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -97,7 +97,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { mReduceBrightColorsController.setReduceBrightColorsActivated(!mState.value); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index f1d8f9f25286..35e43b6fed9e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -29,13 +29,13 @@ import android.os.Looper; import android.provider.Settings; import android.provider.Settings.Secure; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -131,7 +131,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { final boolean newState = !mState.value; mController.setRotationLocked(!newState, /* caller= */ "RotationLockTile#handleClick"); refreshState(newState); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 1a90d439c6d8..4715230a0958 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -23,7 +23,6 @@ import android.os.Looper; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; @@ -32,6 +31,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.logging.MetricsLogger; import com.android.systemui.animation.DialogCuj; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; @@ -118,13 +118,13 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (mController.isStarting()) { cancelCountdown(); } else if (mController.isRecording()) { stopRecording(); } else { - mUiHandler.post(() -> showPrompt(view)); + mUiHandler.post(() -> showPrompt(expandable)); } refreshState(); } @@ -174,10 +174,11 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> return mContext.getString(R.string.quick_settings_screen_record_label); } - private void showPrompt(@Nullable View view) { + private void showPrompt(@Nullable Expandable expandable) { // We animate from the touched view only if we are not on the keyguard, given that if we // are we will dismiss it which will also collapse the shade. - boolean shouldAnimateFromView = view != null && !mKeyguardStateController.isShowing(); + boolean shouldAnimateFromExpandable = + expandable != null && !mKeyguardStateController.isShowing(); // Create the recording dialog that will collapse the shade only if we start the recording. Runnable onStartRecordingClicked = () -> { @@ -192,10 +193,17 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> mDialogTransitionAnimator, mActivityStarter, onStartRecordingClicked); ActivityStarter.OnDismissAction dismissAction = () -> { - if (shouldAnimateFromView) { - mDialogTransitionAnimator.showFromView(dialog, view, new DialogCuj( - InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG), - /* animateBackgroundBoundsChange= */ true); + if (shouldAnimateFromExpandable) { + DialogTransitionAnimator.Controller controller = + expandable.dialogTransitionController(new DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG)); + if (controller != null) { + mDialogTransitionAnimator.show(dialog, + controller, /* animateBackgroundBoundsChange= */ true); + } else { + dialog.show(); + } } else { dialog.show(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index 3eeb2a3303be..036ce080c543 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -26,13 +26,13 @@ import android.provider.Settings; import android.safetycenter.SafetyCenterManager; import android.service.quicksettings.Tile; import android.text.TextUtils; -import android.view.View; import android.widget.Switch; import androidx.annotation.DrawableRes; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -100,7 +100,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { boolean blocked = mSensorPrivacyController.isSensorBlocked(getSensorId()); if (mSensorPrivacyController.requiresAuthentication() && mKeyguard.isMethodSecure() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index d92873adafd8..bec6581e54c9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -24,13 +24,13 @@ import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.TextUtils; -import android.view.View; import android.widget.Switch; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -107,7 +107,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { if (getState().state == Tile.STATE_UNAVAILABLE) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index abc481277e61..d9546ec6ac51 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -24,7 +24,6 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.view.View; import android.widget.Switch; import androidx.annotation.MainThread; @@ -32,6 +31,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.animation.Expandable; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.ActivityStarter; @@ -88,7 +88,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements } @Override - public void handleClick(@Nullable View view) { + public void handleClick(@Nullable Expandable expandable) { mProfileController.setWorkModeEnabled(!mState.value); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt index 7192f58218a4..972b20e138d9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt @@ -20,24 +20,28 @@ import android.app.PendingIntent import android.content.Intent import android.content.pm.PackageManager import android.os.UserHandle -import android.view.View import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.ActivityTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import javax.inject.Inject /** * Provides a shortcut to start an activity from [QSTileUserActionInteractor]. It supports keyguard - * dismissing and tile from-view animations. + * dismissing and tile from-view animations, as well as the option to show over lockscreen. */ interface QSTileIntentUserInputHandler { - fun handle(view: View?, intent: Intent) + fun handle( + expandable: Expandable?, + intent: Intent, + dismissShadeShowOverLockScreenWhenLocked: Boolean = false + ) /** @param requestLaunchingDefaultActivity used in case !pendingIndent.isActivity */ fun handle( - view: View?, + expandable: Expandable?, pendingIntent: PendingIntent, requestLaunchingDefaultActivity: Boolean = false ) @@ -52,31 +56,38 @@ constructor( private val userHandle: UserHandle, ) : QSTileIntentUserInputHandler { - override fun handle(view: View?, intent: Intent) { + override fun handle( + expandable: Expandable?, + intent: Intent, + dismissShadeShowOverLockScreenWhenLocked: Boolean + ) { val animationController: ActivityTransitionAnimator.Controller? = - view?.let { - ActivityTransitionAnimator.Controller.fromView( - it, - InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, - ) - } - activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController) + expandable?.activityTransitionController( + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE + ) + if (dismissShadeShowOverLockScreenWhenLocked) { + activityStarter.startActivity( + intent, + true /* dismissShade */, + animationController, + true /* showOverLockscreenWhenLocked */ + ) + } else { + activityStarter.postStartActivityDismissingKeyguard(intent, 0, animationController) + } } // TODO(b/249804373): make sure to allow showing activities over the lockscreen. See b/292112939 override fun handle( - view: View?, + expandable: Expandable?, pendingIntent: PendingIntent, requestLaunchingDefaultActivity: Boolean ) { if (pendingIntent.isActivity) { val animationController: ActivityTransitionAnimator.Controller? = - view?.let { - ActivityTransitionAnimator.Controller.fromView( - it, - InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, - ) - } + expandable?.activityTransitionController( + InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE + ) activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController) } else if (requestLaunchingDefaultActivity) { val intent = @@ -97,7 +108,7 @@ constructor( ?.let { resolved -> intent.setPackage(null) intent.setComponent(resolved.activityInfo.componentName) - handle(view, intent) + handle(expandable, intent) } } } 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..c971f547c302 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 @@ -15,6 +15,7 @@ */ package com.android.systemui.qs.tiles.dialog; +import static com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI; import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT; @@ -57,6 +58,8 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; +import com.android.internal.telephony.flags.Flags; +import com.android.settingslib.satellite.SatelliteDialogUtils; import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils; import com.android.systemui.Prefs; import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan; @@ -73,6 +76,7 @@ import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; import kotlinx.coroutines.CoroutineScope; +import kotlinx.coroutines.Job; import java.util.List; import java.util.concurrent.Executor; @@ -161,6 +165,9 @@ public class InternetDialogDelegate implements // Wi-Fi scanning progress bar protected boolean mIsProgressBarVisible; private SystemUIDialog mDialog; + private final CoroutineScope mCoroutineScope; + @Nullable + private Job mClickJob; @AssistedFactory public interface Factory { @@ -203,7 +210,7 @@ public class InternetDialogDelegate implements mCanConfigWifi = canConfigWifi; mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context); mKeyguard = keyguardStateController; - + mCoroutineScope = coroutineScope; mUiEventLogger = uiEventLogger; mDialogTransitionAnimator = dialogTransitionAnimator; mAdapter = new InternetAdapter(mInternetDialogController, coroutineScope); @@ -388,11 +395,9 @@ public class InternetDialogDelegate implements }); mConnectedWifListLayout.setOnClickListener(this::onClickConnectedWifi); mSeeAllLayout.setOnClickListener(this::onClickSeeMoreButton); - mWiFiToggle.setOnCheckedChangeListener( - (buttonView, isChecked) -> { - if (mInternetDialogController.isWifiEnabled() == isChecked) return; - mInternetDialogController.setWifiEnabled(isChecked); - }); + mWiFiToggle.setOnClickListener(v -> { + handleWifiToggleClicked(mWiFiToggle.isChecked()); + }); mDoneButton.setOnClickListener(v -> dialog.dismiss()); mShareWifiButton.setOnClickListener(v -> { if (mInternetDialogController.mayLaunchShareWifiSettings(mConnectedWifiEntry, v)) { @@ -404,6 +409,32 @@ public class InternetDialogDelegate implements }); } + private void handleWifiToggleClicked(boolean isChecked) { + if (Flags.oemEnabledSatelliteFlag()) { + if (mClickJob != null && !mClickJob.isCompleted()) { + return; + } + mClickJob = SatelliteDialogUtils.mayStartSatelliteWarningDialog( + mDialog.getContext(), mCoroutineScope, TYPE_IS_WIFI, isAllowClick -> { + if (isAllowClick) { + setWifiEnable(isChecked); + } else { + mWiFiToggle.setChecked(!isChecked); + } + return null; + }); + return; + } + setWifiEnable(isChecked); + } + + private void setWifiEnable(boolean isChecked) { + if (mInternetDialogController.isWifiEnabled() == isChecked) { + return; + } + mInternetDialogController.setWifiEnabled(isChecked); + } + @MainThread private void updateEthernet() { mEthernetLayout.setVisibility( @@ -429,7 +460,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/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt index 5aef950b6a19..246fe3883e19 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt @@ -16,10 +16,10 @@ package com.android.systemui.qs.tiles.dialog import android.util.Log -import android.view.View import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.phone.SystemUIDialog @@ -47,14 +47,14 @@ constructor( } /** - * Creates a [InternetDialogDelegate]. The dialog will be animated from [view] if it is not - * null. + * Creates a [InternetDialogDelegate]. The dialog will be animated from [expandable] if it is + * not null. */ fun create( aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean, - view: View? + expandable: Expandable? ) { if (dialog != null) { if (DEBUG) { @@ -67,20 +67,18 @@ constructor( dialogFactory .create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope) .createDialog() - if (view != null) { - dialogTransitionAnimator.showFromView( + val controller = + expandable?.dialogTransitionController( + DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG) + ) + controller?.let { + dialogTransitionAnimator.show( dialog!!, - view, - animateBackgroundBoundsChange = true, - cuj = - DialogCuj( - InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG - ) + controller, + animateBackgroundBoundsChange = true ) - } else { - dialog!!.show() } + ?: dialog?.show() } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt index 9e13a56c49a1..bf0f8f6577de 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/interactor/AirplaneModeTileUserActionInteractor.kt @@ -45,7 +45,7 @@ constructor( } AirplaneModeInteractor.SetResult.BLOCKED_BY_ECM -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS), ) } @@ -53,7 +53,7 @@ constructor( } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt index 0ad520bd31ee..14fc57c0f83f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/interactor/AlarmTileUserActionInteractor.kt @@ -40,9 +40,12 @@ constructor( data.alarmClockInfo.showIntent != null ) { val pendingIndent = data.alarmClockInfo.showIntent - inputHandler.handle(action.view, pendingIndent, true) + inputHandler.handle(action.expandable, pendingIndent, true) } else { - inputHandler.handle(action.view, Intent(AlarmClock.ACTION_SHOW_ALARMS)) + inputHandler.handle( + action.expandable, + Intent(AlarmClock.ACTION_SHOW_ALARMS) + ) } } is QSTileUserAction.LongClick -> {} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt index 1e4eb38008bd..d4b4fe06ded8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/domain/interactor/BatterySaverTileUserActionInteractor.kt @@ -39,12 +39,12 @@ constructor( when (action) { is QSTileUserAction.Click -> { if (!data.isPluggedIn) { - batteryController.setPowerSaveMode(!data.isPowerSaving, action.view) + batteryController.setPowerSaveMode(!data.isPowerSaving, action.expandable) } } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_BATTERY_SAVER_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt index d1838029db4e..534bd734f5bd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt @@ -45,7 +45,7 @@ constructor( } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_COLOR_CORRECTION_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt index a16ac360e7e4..9bdf6316b069 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt @@ -29,9 +29,9 @@ import android.os.UserHandle import android.provider.Settings import android.service.quicksettings.TileService import android.view.IWindowManager -import android.view.View import android.view.WindowManager import androidx.annotation.GuardedBy +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler @@ -65,20 +65,21 @@ constructor( @GuardedBy("token") private var isTokenGranted: Boolean = false @GuardedBy("token") private var isShowingDialog: Boolean = false - private val lastClickedView: AtomicReference<View> = AtomicReference<View>() + private val lastClickedExpandable: AtomicReference<Expandable> = AtomicReference<Expandable>() override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) = with(input) { when (action) { - is QSTileUserAction.Click -> click(action.view, data.tile.activityLaunchForClick) + is QSTileUserAction.Click -> + click(action.expandable, data.tile.activityLaunchForClick) is QSTileUserAction.LongClick -> - longClick(user, action.view, data.componentName, data.tile.state) + longClick(user, action.expandable, data.componentName, data.tile.state) } qsTileLogger.logCustomTileUserActionDelivered(tileSpec) } private suspend fun click( - view: View?, + expandable: Expandable?, activityLaunchForClick: PendingIntent?, ) { grantToken() @@ -86,10 +87,10 @@ constructor( // Bind active tile to deliver user action serviceInteractor.bindOnClick() if (activityLaunchForClick == null) { - lastClickedView.set(view) + lastClickedExpandable.set(expandable) serviceInteractor.onClick(token) } else { - qsTileIntentUserInputHandler.handle(view, activityLaunchForClick) + qsTileIntentUserInputHandler.handle(expandable, activityLaunchForClick) } } catch (e: RemoteException) { qsTileLogger.logError(tileSpec, "Failed to deliver click", e) @@ -117,10 +118,10 @@ constructor( if (!isTokenGranted) { return } - qsTileIntentUserInputHandler.handle(lastClickedView.getAndSet(null), pendingIntent) + qsTileIntentUserInputHandler.handle(lastClickedExpandable.getAndSet(null), pendingIntent) } - fun clearLastClickedView() = lastClickedView.set(null) + fun clearLastClickedView() = lastClickedExpandable.set(null) private fun grantToken() { synchronized(token) { @@ -142,7 +143,7 @@ constructor( private suspend fun longClick( user: UserHandle, - view: View?, + expandable: Expandable?, componentName: ComponentName, state: Int ) { @@ -159,14 +160,14 @@ constructor( } if (resolvedIntent == null) { qsTileIntentUserInputHandler.handle( - view, + expandable, Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) .setData( Uri.fromParts(IntentFilter.SCHEME_PACKAGE, componentName.packageName, null) ) ) } else { - qsTileIntentUserInputHandler.handle(view, resolvedIntent) + qsTileIntentUserInputHandler.handle(expandable, resolvedIntent) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt index db8b1a5a5d47..d308ec889136 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/interactor/FontScalingTileUserActionInteractor.kt @@ -52,21 +52,22 @@ constructor( with(input) { when (action) { is QSTileUserAction.Click -> { - // We animate from the touched view only if we are not on the keyguard - val animateFromView: Boolean = - action.view != null && !keyguardStateController.isShowing + // We animate from the touched expandable only if we are not on the keyguard + val animateFromExpandable: Boolean = + action.expandable != null && !keyguardStateController.isShowing val runnable = Runnable { val dialog: SystemUIDialog = fontScalingDialogDelegateProvider.get().createDialog() - if (animateFromView) { - dialogTransitionAnimator.showFromView( - dialog, - action.view!!, - DialogCuj( - InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG + if (animateFromExpandable) { + action.expandable + ?.dialogTransitionController( + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG + ) ) - ) + ?.let { dialogTransitionAnimator.show(dialog, it) } + ?: dialog.show() } else { dialog.show() } @@ -84,7 +85,7 @@ constructor( } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_TEXT_READING_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt index 2620cd555d8e..c0b089d84dc8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/interactor/InternetTileUserActionInteractor.kt @@ -49,13 +49,13 @@ constructor( aboveStatusBar = true, accessPointController.canConfigMobileData(), accessPointController.canConfigWifi(), - action.view, + action.expandable, ) } } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_WIFI_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt index 43b58c83f7ef..d64327333cb6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/interactor/ColorInversionUserActionInteractor.kt @@ -45,7 +45,7 @@ constructor( } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_COLOR_INVERSION_SETTINGS) ) } 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/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt index 66705ead3cf0..77404aa82606 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt @@ -64,7 +64,7 @@ constructor( } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) ) } 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/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt new file mode 100644 index 000000000000..8c0fd2cd672a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileDataInteractor.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.onehanded.domain + +import android.os.UserHandle +import com.android.systemui.accessibility.data.repository.OneHandedModeRepository +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.onehanded.domain.model.OneHandedModeTileModel +import com.android.wm.shell.onehanded.OneHanded +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map + +/** Observes one handed mode state changes providing the [OneHandedModeTileModel]. */ +class OneHandedModeTileDataInteractor +@Inject +constructor( + private val oneHandedModeRepository: OneHandedModeRepository, +) : QSTileDataInteractor<OneHandedModeTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<OneHandedModeTileModel> { + return oneHandedModeRepository.isEnabled(user).map { OneHandedModeTileModel(it) } + } + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(OneHanded.sIsSupportOneHandedMode) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt new file mode 100644 index 000000000000..5cb0e181378b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/OneHandedModeTileUserActionInteractor.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.onehanded.domain + +import android.content.Intent +import android.provider.Settings +import com.android.systemui.accessibility.data.repository.OneHandedModeRepository +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Handles one handed mode tile clicks. */ +class OneHandedModeTileUserActionInteractor +@Inject +constructor( + private val oneHandedModeRepository: OneHandedModeRepository, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<OneHandedModeTileModel> { + + override suspend fun handleInput(input: QSTileInput<OneHandedModeTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + oneHandedModeRepository.setIsEnabled( + !data.isEnabled, + user, + ) + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.expandable, + Intent(Settings.ACTION_ONE_HANDED_SETTINGS) + ) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.kt new file mode 100644 index 000000000000..7cebdfe84e2d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/domain/model/OneHandedModeTileModel.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.onehanded.domain.model + +/** + * One handed mode tile model. + * + * @param isEnabled is true when one handed mode is enabled; + */ +@JvmInline value class OneHandedModeTileModel(val isEnabled: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt new file mode 100644 index 000000000000..9166ed8faacf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.onehanded.ui + +import android.content.res.Resources +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.onehanded.domain.model.OneHandedModeTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [OneHandedModeTileModel] to [QSTileState]. */ +class OneHandedModeTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, +) : QSTileDataToStateMapper<OneHandedModeTileModel> { + + override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded) + label = resources.getString(R.string.quick_settings_onehanded_label) + icon = { + Icon.Loaded( + resources.getDrawable( + com.android.internal.R.drawable.ic_qs_one_handed_mode, + theme + ), + null + ) + } + if (data.isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = subtitleArray[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = subtitleArray[1] + } + sideViewIcon = QSTileState.SideViewIcon.None + contentDescription = label + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt new file mode 100644 index 000000000000..1e8ce588b4e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileDataInteractor.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.domain.interactor + +import android.os.UserHandle +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE +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.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +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.flowOf +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn + +/** Observes one qr scanner state changes providing the [QRCodeScannerTileModel]. */ +class QRCodeScannerTileDataInteractor +@Inject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + @Application private val scope: CoroutineScope, + private val qrController: QRCodeScannerController, +) : QSTileDataInteractor<QRCodeScannerTileModel> { + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<QRCodeScannerTileModel> = + conflatedCallbackFlow { + qrController.registerQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE) + val callback = + object : QRCodeScannerController.Callback { + override fun onQRCodeScannerActivityChanged() { + trySend(generateModel()) + } + } + qrController.addCallback(callback) + awaitClose { + qrController.removeCallback(callback) + qrController.unregisterQRCodeScannerChangeObservers( + DEFAULT_QR_CODE_SCANNER_CHANGE + ) + } + } + .onStart { emit(generateModel()) } + .flowOn(bgCoroutineContext) + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + QRCodeScannerTileModel.TemporarilyUnavailable + ) + + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(qrController.isCameraAvailable) + + private fun generateModel(): QRCodeScannerTileModel { + val intent = qrController.intent + + return if (qrController.isAbleToLaunchScannerActivity && intent != null) + QRCodeScannerTileModel.Available(intent) + else QRCodeScannerTileModel.TemporarilyUnavailable + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.kt new file mode 100644 index 000000000000..7c0c41eca4bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/interactor/QRCodeScannerTileUserActionInteractor.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.tiles.impl.qr.domain.interactor + +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject + +/** Handles qr tile clicks. */ +class QRCodeScannerTileUserActionInteractor +@Inject +constructor( + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, +) : QSTileUserActionInteractor<QRCodeScannerTileModel> { + + override suspend fun handleInput(input: QSTileInput<QRCodeScannerTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + when (data) { + is QRCodeScannerTileModel.Available -> + qsTileIntentUserActionHandler.handle( + action.expandable, + data.intent, + true + ) + is QRCodeScannerTileModel.TemporarilyUnavailable -> {} // no-op + } + } + is QSTileUserAction.LongClick -> {} // no-op + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.kt new file mode 100644 index 000000000000..22c9b66b2806 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/domain/model/QRCodeScannerTileModel.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.qs.tiles.impl.qr.domain.model + +import android.content.Intent + +/** qr scanner tile model. */ +sealed interface QRCodeScannerTileModel { + data class Available(val intent: Intent) : QRCodeScannerTileModel + data object TemporarilyUnavailable : QRCodeScannerTileModel +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt new file mode 100644 index 000000000000..45a77179fb3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr.ui + +import android.content.res.Resources +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.qr.domain.model.QRCodeScannerTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [QRCodeScannerTileModel] to [QSTileState]. */ +class QRCodeScannerTileMapper +@Inject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, +) : QSTileDataToStateMapper<QRCodeScannerTileModel> { + + override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + label = resources.getString(R.string.qr_code_scanner_title) + contentDescription = label + icon = { + Icon.Loaded(resources.getDrawable(R.drawable.ic_qr_code_scanner, theme), null) + } + sideViewIcon = QSTileState.SideViewIcon.Chevron + supportedActions = setOf(QSTileState.UserAction.CLICK) + + when (data) { + is QRCodeScannerTileModel.Available -> { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = null + } + is QRCodeScannerTileModel.TemporarilyUnavailable -> { + activationState = QSTileState.ActivationState.UNAVAILABLE + secondaryLabel = + resources.getString(R.string.qr_code_scanner_updating_secondary_label) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt index 762f8635c562..14dbe0e8a69a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/domain/interactor/ReduceBrightColorsTileUserActionInteractor.kt @@ -44,7 +44,7 @@ constructor( } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_REDUCE_BRIGHT_COLORS_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt index 8530926e68e0..34385ea815eb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt @@ -42,7 +42,7 @@ constructor( } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt index 861faf5a0e4f..a5dc66c901f1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt @@ -75,34 +75,32 @@ constructor( // must be created and shown on the main thread, so we post it to the UI // handler withContext(coroutineContext) { - val dialogContext = action.view?.context ?: context val dialogDelegate = DataSaverDialogDelegate( systemUIDialogFactory, - dialogContext, + context, backgroundContext, dataSaverController, sharedPreferences ) - val dialog = systemUIDialogFactory.create(dialogDelegate, dialogContext) + val dialog = systemUIDialogFactory.create(dialogDelegate, context) - if (action.view != null) { - dialogTransitionAnimator.showFromView( - dialog, - action.view!!, + action.expandable + ?.dialogTransitionController( DialogCuj( InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG ) ) - } else { - dialog.show() - } + ?.let { controller -> + dialogTransitionAnimator.show(dialog, controller) + } + ?: dialog.show() } } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_DATA_SAVER_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt index d2bd09fe0ea3..79766d6642b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/interactor/ScreenRecordTileUserActionInteractor.kt @@ -18,10 +18,10 @@ package com.android.systemui.qs.tiles.impl.screenrecord.domain.interactor import android.content.Context import android.util.Log -import android.view.View import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -68,14 +68,16 @@ constructor( is ScreenRecordTileModel.Recording -> withContext(backgroundContext) { recordingController.stopRecording() } is ScreenRecordTileModel.DoingNothing -> - withContext(mainContext) { showPrompt(action.view, user.identifier) } + withContext(mainContext) { + showPrompt(action.expandable, user.identifier) + } } } is QSTileUserAction.LongClick -> {} // no-op } } - private fun showPrompt(view: View?, userId: Int) { + private fun showPrompt(expandable: Expandable?, userId: Int) { // Create the recording dialog that will collapse the shade only if we start the recording. val onStartRecordingClicked = Runnable { // We dismiss the shade. Since starting the recording will also dismiss the dialog, we @@ -99,21 +101,29 @@ constructor( return } - // We animate from the touched view only if we are not on the keyguard, given that if we + // We animate from the touched expandable only if we are not on the keyguard, given that if + // we // are we will dismiss it which will also collapse the shade. - val shouldAnimateFromView = view != null && !keyguardInteractor.isKeyguardShowing() + val shouldAnimateFromExpandable = + expandable != null && !keyguardInteractor.isKeyguardShowing() val dismissAction = ActivityStarter.OnDismissAction { - if (shouldAnimateFromView) { - dialogTransitionAnimator.showFromView( - dialog, - view!!, - DialogCuj( - InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG - ), - animateBackgroundBoundsChange = true - ) + if (shouldAnimateFromExpandable) { + val controller = + expandable?.dialogTransitionController( + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG + ) + ) + controller?.let { + dialogTransitionAnimator.show( + dialog, + controller, + animateBackgroundBoundsChange = true, + ) + } + ?: dialog.show() } else { dialog.show() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt index 9711cb81e2c0..f22a4269b3f5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt @@ -80,7 +80,7 @@ constructor( } ) } - qsTileIntentUserActionHandler.handle(action.view, longClickIntent) + qsTileIntentUserActionHandler.handle(action.expandable, longClickIntent) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt index 00d7a629f5be..f8dd1730cc10 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/interactor/UiModeNightTileUserActionInteractor.kt @@ -50,7 +50,7 @@ constructor( } is QSTileUserAction.LongClick -> { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_DARK_THEME_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt index f765f8b3ac77..031e4d978739 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/domain/interactor/WorkModeTileUserActionInteractor.kt @@ -44,7 +44,7 @@ constructor( is QSTileUserAction.LongClick -> { if (data is WorkModeTileModel.HasActiveProfile) { qsTileIntentUserActionHandler.handle( - action.view, + action.expandable, Intent(Settings.ACTION_MANAGED_PROFILE_SETTINGS) ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt index a1450420131b..acb29362681b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt @@ -16,12 +16,12 @@ package com.android.systemui.qs.tiles.viewmodel -import android.view.View +import com.android.systemui.animation.Expandable sealed interface QSTileUserAction { - val view: View? + val expandable: Expandable? - class Click(override val view: View?) : QSTileUserAction - class LongClick(override val view: View?) : QSTileUserAction + class Click(override val expandable: Expandable?) : QSTileUserAction + class LongClick(override val expandable: Expandable?) : QSTileUserAction } 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 5a389f3fe701..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 @@ -19,10 +19,10 @@ package com.android.systemui.qs.tiles.viewmodel import android.content.Context import android.os.UserHandle import android.util.Log -import android.view.View import androidx.annotation.GuardedBy import com.android.internal.logging.InstanceId import com.android.systemui.Dumpable +import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.plugins.qs.QSTile @@ -126,21 +126,21 @@ constructor( synchronized(callbacks) { callbacks.clear() } } - override fun click(view: View?) { + override fun click(expandable: Expandable?) { if (isActionSupported(QSTileState.UserAction.CLICK)) { - qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view)) + qsTileViewModel.onActionPerformed(QSTileUserAction.Click(expandable)) } } - override fun secondaryClick(view: View?) { + override fun secondaryClick(expandable: Expandable?) { if (isActionSupported(QSTileState.UserAction.CLICK)) { - qsTileViewModel.onActionPerformed(QSTileUserAction.Click(view)) + qsTileViewModel.onActionPerformed(QSTileUserAction.Click(expandable)) } } - override fun longClick(view: View?) { + override fun longClick(expandable: Expandable?) { if (isActionSupported(QSTileState.UserAction.LONG_CLICK)) { - qsTileViewModel.onActionPerformed(QSTileUserAction.LongClick(view)) + qsTileViewModel.onActionPerformed(QSTileUserAction.LongClick(expandable)) } } @@ -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/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt index 3d86e3c084f8..fb872d538e6c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt @@ -49,21 +49,44 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext // TODO(307945185) Split View concerns into a ViewBinder /** Adapter to use between Scene system and [QSImpl] */ interface QSSceneAdapter { - /** Whether [QSImpl] is currently customizing */ + + /** + * Whether we are currently customizing or entering the customizer. + * + * @see CustomizerState.isCustomizing + */ val isCustomizing: StateFlow<Boolean> /** + * Whether the customizer is showing. This includes animating into and out of it. + * + * @see CustomizerState.isShowing + */ + val isCustomizerShowing: StateFlow<Boolean> + + /** + * The duration of the current animation in/out of customizer. If not in an animating state, + * this duration is 0 (to match show/hide immediately). + * + * @see CustomizerState.Animating.animationDuration + */ + val customizerAnimationDuration: StateFlow<Int> + + /** * A view with the QS content ([QSContainerImpl]), managed by an instance of [QSImpl] tracked by * the interactor. */ @@ -104,28 +127,28 @@ interface QSSceneAdapter { val isVisible: Boolean val expansion: Float - val squishiness: Float + val squishiness: () -> Float data object CLOSED : State { override val isVisible = false override val expansion = 0f - override val squishiness = 1f + override val squishiness = { 1f } } /** State for expanding between QQS and QS */ data class Expanding(override val expansion: Float) : State { override val isVisible = true - override val squishiness = 1f + override val squishiness = { 1f } } /** State for appearing QQS from Lockscreen or Gone */ - data class UnsquishingQQS(override val squishiness: Float) : State { + data class UnsquishingQQS(override val squishiness: () -> Float) : State { override val isVisible = true override val expansion = 0f } /** State for appearing QS from Lockscreen or Gone, used in Split shade */ - data class UnsquishingQS(override val squishiness: Float) : State { + data class UnsquishingQS(override val squishiness: () -> Float) : State { override val isVisible = true override val expansion = 1f } @@ -181,8 +204,35 @@ constructor( onBufferOverflow = BufferOverflow.DROP_OLDEST, ) private val state = MutableStateFlow<QSSceneAdapter.State>(QSSceneAdapter.State.CLOSED) - private val _isCustomizing: MutableStateFlow<Boolean> = MutableStateFlow(false) - override val isCustomizing = _isCustomizing.asStateFlow() + private val _customizingState: MutableStateFlow<CustomizerState> = + MutableStateFlow(CustomizerState.Hidden) + val customizerState = _customizingState.asStateFlow() + + override val isCustomizing: StateFlow<Boolean> = + customizerState + .map { it.isCustomizing } + .stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + customizerState.value.isCustomizing, + ) + override val isCustomizerShowing: StateFlow<Boolean> = + customizerState + .map { it.isShowing } + .stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + customizerState.value.isShowing + ) + override val customizerAnimationDuration: StateFlow<Int> = + customizerState + .map { (it as? CustomizerState.Animating)?.animationDuration?.toInt() ?: 0 } + .stateIn( + applicationScope, + SharingStarted.WhileSubscribed(), + (customizerState.value as? CustomizerState.Animating)?.animationDuration?.toInt() + ?: 0, + ) private val _qsImpl: MutableStateFlow<QSImpl?> = MutableStateFlow(null) val qsImpl = _qsImpl.asStateFlow() @@ -209,9 +259,9 @@ constructor( dumpManager.registerDumpable(this) applicationScope.launch { launch { - state.sample(_isCustomizing, ::Pair).collect { (state, customizing) -> + state.sample(_customizingState, ::Pair).collect { (state, customizing) -> qsImpl.value?.apply { - if (state != QSSceneAdapter.State.QS && customizing) { + if (state != QSSceneAdapter.State.QS && customizing.isShowing) { this@apply.closeCustomizerImmediately() } applyState(state) @@ -243,14 +293,38 @@ constructor( } } - override fun setCustomizerAnimating(animating: Boolean) {} + override fun setCustomizerAnimating(animating: Boolean) { + if (_customizingState.value is CustomizerState.Animating && !animating) { + _customizingState.update { + if (it is CustomizerState.AnimatingIntoCustomizer) { + CustomizerState.Showing + } else { + CustomizerState.Hidden + } + } + } + } override fun setCustomizerShowing(showing: Boolean) { - _isCustomizing.value = showing + setCustomizerShowing(showing, 0L) } override fun setCustomizerShowing(showing: Boolean, animationDuration: Long) { - setCustomizerShowing(showing) + _customizingState.update { _ -> + if (showing) { + if (animationDuration > 0) { + CustomizerState.AnimatingIntoCustomizer(animationDuration) + } else { + CustomizerState.Showing + } + } else { + if (animationDuration > 0) { + CustomizerState.AnimatingOutOfCustomizer(animationDuration) + } else { + CustomizerState.Hidden + } + } + } } override fun setDetailShowing(showing: Boolean) {} @@ -296,15 +370,56 @@ constructor( setQsVisible(state.isVisible) setExpanded(state.isVisible && state.expansion > 0f) setListening(state.isVisible) - setQsExpansion(state.expansion, 1f, 0f, state.squishiness) + setQsExpansion(state.expansion, 1f, 0f, state.squishiness()) } override fun dump(pw: PrintWriter, args: Array<out String>) { pw.apply { println("Last state: ${state.value}") - println("Customizing: ${isCustomizing.value}") + println("CustomizerState: ${_customizingState.value}") println("QQS height: $qqsHeight") println("QS height: $qsHeight") } } } + +/** Current state of the customizer */ +sealed interface CustomizerState { + + /** + * This indicates that some part of the customizer is showing. It could be animating in or out. + */ + val isShowing: Boolean + get() = true + + /** + * This indicates that we are currently customizing or animating into it. In particular, when + * animating out, this is false. + * + * @see QSCustomizer.isCustomizing + */ + val isCustomizing: Boolean + get() = false + + sealed interface Animating : CustomizerState { + val animationDuration: Long + } + + /** Customizer is completely hidden, and not animating */ + data object Hidden : CustomizerState { + override val isShowing = false + } + + /** Customizer is completely showing, and not animating */ + data object Showing : CustomizerState { + override val isCustomizing = true + } + + /** Animating from [Hidden] into [Showing]. */ + data class AnimatingIntoCustomizer(override val animationDuration: Long) : Animating { + override val isCustomizing = true + } + + /** Animating from [Showing] into [Hidden]. */ + data class AnimatingOutOfCustomizer(override val animationDuration: Long) : Animating +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt index d6325c049823..a04fa3817493 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.ui.viewmodel import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel import javax.inject.Inject @@ -27,4 +28,5 @@ class QuickSettingsContainerViewModel constructor( val brightnessSliderViewModel: BrightnessSliderViewModel, val tileGridViewModel: TileGridViewModel, + val editModeViewModel: EditModeViewModel, ) 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 257c4d58f569..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 @@ -79,13 +79,13 @@ constructor( combine( deviceEntryInteractor.isUnlocked, deviceEntryInteractor.canSwipeToEnter, - qsSceneAdapter.isCustomizing, + qsSceneAdapter.isCustomizerShowing, backScene, - ) { isUnlocked, canSwipeToDismiss, isCustomizing, backScene -> + ) { isUnlocked, canSwipeToDismiss, isCustomizerShowing, backScene -> destinationScenes( isUnlocked, canSwipeToDismiss, - isCustomizing, + isCustomizerShowing, backScene, ) } @@ -96,11 +96,13 @@ constructor( destinationScenes( isUnlocked = deviceEntryInteractor.isUnlocked.value, canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value, - isCustomizing = qsSceneAdapter.isCustomizing.value, + isCustomizing = qsSceneAdapter.isCustomizerShowing.value, backScene = backScene.value, ), ) + 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/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt new file mode 100644 index 000000000000..d48d55dd9918 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.ui.viewmodel + +import com.android.compose.animation.scene.Back +import com.android.compose.animation.scene.SceneKey +import com.android.compose.animation.scene.Swipe +import com.android.compose.animation.scene.UserAction +import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** Models UI state and handles user input for the Quick Settings Shade scene. */ +@SysUISingleton +class QuickSettingsShadeSceneViewModel +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + overlayShadeViewModel: OverlayShadeViewModel, +) { + val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = + overlayShadeViewModel.backgroundScene + .map(::destinationScenes) + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = destinationScenes(overlayShadeViewModel.backgroundScene.value), + ) + + private fun destinationScenes(backgroundScene: SceneKey): Map<UserAction, UserActionResult> { + return mapOf( + Swipe.Up to backgroundScene, + Back to backgroundScene, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index b92e8eb72353..0327ec760ace 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -37,7 +37,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_F import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_TRANSITION; @@ -74,6 +73,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.annotation.NonNull; +import com.android.compose.animation.scene.SceneKey; import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AssistUtils; @@ -103,16 +103,19 @@ import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.ShadeViewController; +import com.android.systemui.shade.domain.interactor.ShadeInteractor; +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; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; -import com.android.wm.shell.desktopmode.DesktopModeStatus; +import com.android.wm.shell.shared.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInterface; import dagger.Lazy; @@ -155,6 +158,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final ScreenPinningRequest mScreenPinningRequest; private final NotificationShadeWindowController mStatusBarWinController; private final Provider<SceneInteractor> mSceneInteractor; + private final Provider<ShadeInteractor> mShadeInteractor; private final Runnable mConnectionRunnable = () -> internalConnectToCurrentUser("runnable: startConnectionToCurrentUser"); @@ -243,7 +247,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Gesture was too short to be picked up by scene container touch // handling; programmatically start the transition to shade scene. mSceneInteractor.get().changeScene( - Scenes.Shade, "short launcher swipe"); + getShadeSceneKey(), + "short launcher swipe" + ); } } event.recycle(); @@ -261,7 +267,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis "trackpad swipe"); } else if (action == ACTION_UP) { mSceneInteractor.get().changeScene( - Scenes.Shade, "short trackpad swipe"); + getShadeSceneKey(), + "short trackpad swipe" + ); } mStatusBarWinController.getWindowRootView().dispatchTouchEvent(event); } else { @@ -407,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) { @@ -618,6 +632,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis NotificationShadeWindowController statusBarWinController, SysUiState sysUiState, Provider<SceneInteractor> sceneInteractor, + Provider<ShadeInteractor> shadeInteractor, UserTracker userTracker, WakefulnessLifecycle wakefulnessLifecycle, UiEventLogger uiEventLogger, @@ -644,6 +659,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis mScreenPinningRequest = screenPinningRequest; mStatusBarWinController = statusBarWinController; mSceneInteractor = sceneInteractor; + mShadeInteractor = shadeInteractor; mUserTracker = userTracker; mConnectionBackoffAttempts = 0; mRecentsComponentName = ComponentName.unflattenFromString(context.getString( @@ -691,15 +707,15 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis // Listen for tracing state changes @Override public void onTracingStateChanged(boolean enabled) { - mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled) - .commitUpdate(mContext.getDisplayId()); + // TODO(b/286509643) Cleanup callers of this; Unused downstream } @Override public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) { if (mOverviewProxy != null) { try { - if (DesktopModeStatus.isEnabled() && (sysUiState.getFlags() + if (DesktopModeStatus.canEnterDesktopMode(mContext) + && (sysUiState.getFlags() & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) { return; } @@ -756,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); @@ -909,6 +925,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + private SceneKey getShadeSceneKey() { + return mShadeInteractor.get().getShadeMode().getValue() == ShadeMode.dual() + ? Scenes.NotificationsShade + : Scenes.Shade; + } + private void notifyHomeRotationEnabled(boolean enabled) { for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { mConnectionCallbacks.get(i).onHomeRotationEnabled(enabled); diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 4e290e699ac4..6694878cea74 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt @@ -20,7 +20,6 @@ import android.app.IActivityManager import android.app.NotificationManager import android.content.Context import android.content.Intent -import android.content.pm.LauncherApps import android.content.res.Resources import android.net.Uri import android.os.Handler @@ -63,7 +62,6 @@ constructor( private val panelInteractor: PanelInteractor, private val issueRecordingState: IssueRecordingState, private val iActivityManager: IActivityManager, - private val launcherApps: LauncherApps, ) : RecordingService( controller, @@ -85,7 +83,7 @@ constructor( when (intent?.action) { ACTION_START -> { TraceUtils.traceStart( - contentResolver, + this, DEFAULT_TRACE_TAGS, DEFAULT_BUFFER_SIZE, DEFAULT_IS_INCLUDING_WINSCOPE, @@ -104,11 +102,7 @@ constructor( } ACTION_STOP, ACTION_STOP_NOTIF -> { - // ViewCapture needs to save it's data before it is disabled, or else the data will - // be lost. This is expected to change in the near future, and when that happens - // this line should be removed. - launcherApps.saveViewCaptureData() - TraceUtils.traceStop(contentResolver) + TraceUtils.traceStop(this) issueRecordingState.isRecording = false } ACTION_SHARE -> { @@ -142,7 +136,7 @@ constructor( private fun shareRecording(screenRecording: Uri?) { val traces = - TraceUtils.traceDump(contentResolver, TRACE_FILE_NAME).getOrElse { + TraceUtils.traceDump(this, TRACE_FILE_NAME).getOrElse { Log.v( TAG, "Traces were not present. This can happen if users double" + diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt index 063a52c43a1b..8169dec9ede6 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt @@ -17,10 +17,12 @@ package com.android.systemui.scene import com.android.systemui.CoreStartable +import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shared.flag.DualShade import dagger.Binds import dagger.Module import dagger.Provides @@ -33,6 +35,8 @@ import dagger.multibindings.IntoMap [ EmptySceneModule::class, GoneSceneModule::class, + NotificationsShadeSceneModule::class, + NotificationsShadeSessionModule::class, QuickSettingsSceneModule::class, ShadeSceneModule::class, ], @@ -59,18 +63,24 @@ interface KeyguardlessSceneContainerFrameworkModule { // Note that this list is in z-order. The first one is the bottom-most and the // last one is top-most. sceneKeys = - listOf( + listOfNotNull( Scenes.Gone, - Scenes.QuickSettings, - Scenes.Shade, + Scenes.QuickSettings.takeUnless { DualShade.isEnabled }, + Scenes.QuickSettingsShade.takeIf { DualShade.isEnabled }, + Scenes.NotificationsShade.takeIf { DualShade.isEnabled }, + Scenes.Shade.takeUnless { DualShade.isEnabled }, ), initialSceneKey = Scenes.Gone, navigationDistances = mapOf( - Scenes.Gone to 0, - Scenes.Shade to 1, - Scenes.QuickSettings to 2, - ), + Scenes.Gone to 0, + Scenes.NotificationsShade to 1.takeIf { DualShade.isEnabled }, + Scenes.Shade to 1.takeUnless { DualShade.isEnabled }, + Scenes.QuickSettingsShade to 2.takeIf { DualShade.isEnabled }, + Scenes.QuickSettings to 2.takeUnless { DualShade.isEnabled }, + ) + .filterValues { it != null } + .mapValues { checkNotNull(it.value) } ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index cd1b96508cce..9bd2694f7e82 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -18,10 +18,12 @@ package com.android.systemui.scene import com.android.systemui.CoreStartable import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule +import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shared.flag.DualShade import dagger.Binds import dagger.Module import dagger.Provides @@ -40,6 +42,9 @@ import dagger.multibindings.IntoMap LockscreenSceneModule::class, QuickSettingsSceneModule::class, ShadeSceneModule::class, + QuickSettingsShadeSceneModule::class, + NotificationsShadeSceneModule::class, + NotificationsShadeSessionModule::class, ], ) interface SceneContainerFrameworkModule { @@ -61,27 +66,33 @@ interface SceneContainerFrameworkModule { @Provides fun containerConfig(): SceneContainerConfig { return SceneContainerConfig( - // Note that this list is in z-order. The first one is the bottom-most and the - // last one is top-most. + // Note that this list is in z-order. The first one is the bottom-most and the last + // one is top-most. sceneKeys = - listOf( + listOfNotNull( Scenes.Gone, Scenes.Communal, Scenes.Lockscreen, Scenes.Bouncer, - Scenes.QuickSettings, - Scenes.Shade, + Scenes.QuickSettings.takeUnless { DualShade.isEnabled }, + Scenes.QuickSettingsShade.takeIf { DualShade.isEnabled }, + Scenes.NotificationsShade.takeIf { DualShade.isEnabled }, + Scenes.Shade.takeUnless { DualShade.isEnabled }, ), initialSceneKey = Scenes.Lockscreen, navigationDistances = mapOf( - Scenes.Gone to 0, - Scenes.Lockscreen to 0, - Scenes.Communal to 1, - Scenes.Shade to 2, - Scenes.QuickSettings to 3, - Scenes.Bouncer to 4, - ), + Scenes.Gone to 0, + Scenes.Lockscreen to 0, + Scenes.Communal to 1, + Scenes.NotificationsShade to 2.takeIf { DualShade.isEnabled }, + Scenes.Shade to 2.takeUnless { DualShade.isEnabled }, + Scenes.QuickSettingsShade to 3.takeIf { DualShade.isEnabled }, + Scenes.QuickSettings to 3.takeUnless { DualShade.isEnabled }, + Scenes.Bouncer to 4, + ) + .filterValues { it != null } + .mapValues { checkNotNull(it.value) } ) } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt new file mode 100644 index 000000000000..d3e529c9035b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/data/model/SceneStack.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.data.model + +import com.android.compose.animation.scene.SceneKey + +/** An immutable stack of [SceneKey]s backed by a singly-linked list. */ +sealed interface SceneStack + +private data object EmptyStack : SceneStack + +private data class StackedNodes(val head: SceneKey, val tail: SceneStack) : SceneStack + +/** Returns the scene at the head of the stack, or `null` if empty. O(1) */ +fun SceneStack.peek(): SceneKey? = + when (this) { + EmptyStack -> null + is StackedNodes -> head + } + +/** Returns a stack with the head removed, or `null` if empty. O(1) */ +fun SceneStack.pop(): SceneStack? = + when (this) { + EmptyStack -> null + is StackedNodes -> tail + } + +/** Returns a stack with [sceneKey] as the head on top of [this]. O(1) */ +fun SceneStack.push(sceneKey: SceneKey): SceneStack = StackedNodes(sceneKey, this) + +/** Returns an iterable that produces all elements in the stack, from head to tail. */ +fun SceneStack.asIterable(): Iterable<SceneKey> = Iterable { + iterator { + when (this@asIterable) { + EmptyStack -> {} + is StackedNodes -> { + yield(head) + yieldAll(tail.asIterable()) + } + } + } +} + +/** + * Returns a new [SceneStack] containing the given [scenes], ordered such that the first argument is + * the head returned from [peek], then the second, and so forth. + */ +fun sceneStackOf(vararg scenes: SceneKey): SceneStack { + var result: SceneStack = EmptyStack + for (sceneKey in scenes.reversed()) { + result = result.push(sceneKey) + } + return result +} 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 5748ad459ed6..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 @@ -87,6 +89,14 @@ constructor( ) } + fun snapToScene( + toScene: SceneKey, + ) { + dataSource.snapToScene( + toScene = toScene, + ) + } + /** Sets whether the container is visible. */ fun setVisible(isVisible: Boolean) { _isVisible.value = isVisible diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt index f66d08f781f6..c176ccad7e74 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt @@ -18,13 +18,21 @@ package com.android.systemui.scene.domain.interactor import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.scene.data.model.SceneStack +import com.android.systemui.scene.data.model.asIterable +import com.android.systemui.scene.data.model.peek +import com.android.systemui.scene.data.model.pop +import com.android.systemui.scene.data.model.push +import com.android.systemui.scene.data.model.sceneStackOf import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.SceneContainerConfig -import java.util.Stack import javax.inject.Inject +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.update @SysUISingleton class SceneBackInteractor @@ -33,7 +41,9 @@ constructor( private val logger: SceneLogger, private val sceneContainerConfig: SceneContainerConfig, ) { - private val _backScene = MutableStateFlow<SceneKey?>(null) + private val _backStack = MutableStateFlow(sceneStackOf()) + val backStack: StateFlow<SceneStack> = _backStack.asStateFlow() + /** * The scene to navigate to when the user triggers back navigation. * @@ -44,30 +54,30 @@ constructor( * illegal state to have scene implementation map to itself in its destination scene flow. Thus, * scene implementations might wish to filter their own scene key out before using this. */ - val backScene: StateFlow<SceneKey?> = _backScene.asStateFlow() - - private val backStack = Stack<SceneKey>() + val backScene: Flow<SceneKey?> = backStack.map { it.peek() } fun onSceneChange(from: SceneKey, to: SceneKey) { check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" } when (stackOperation(from, to)) { Clear -> { - backStack.clear() + _backStack.value = sceneStackOf() } Push -> { - backStack.push(from) + _backStack.update { s -> s.push(from) } } Pop -> { - check(backStack.isNotEmpty()) { "Cannot pop ${from.debugName} when stack is empty" } - val popped = backStack.pop() - check(to == popped) { - "Expected to pop ${to.debugName} but instead popped ${popped.debugName}" + _backStack.update { s -> + checkNotNull(s.pop()) { "Cannot pop ${from.debugName} when stack is empty" } + .also { + val popped = s.peek() + check(popped == to) { + "Expected to pop ${to.debugName} but instead popped ${popped?.debugName}" + } + } } } } - - logger.logSceneBackStack(backStack) - _backScene.value = peek() + logger.logSceneBackStack(backStack.value.asIterable()) } private fun stackOperation(from: SceneKey, to: SceneKey): StackOperation { @@ -92,14 +102,6 @@ constructor( } } - private fun peek(): SceneKey? { - return if (backStack.isNotEmpty()) { - backStack.peek() - } else { - null - } - } - private sealed interface StackOperation private data object Clear : StackOperation private data object Push : StackOperation diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt index 5d60373c6e13..6bcd92316106 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneContainerOcclusionInteractor.kt @@ -108,7 +108,7 @@ constructor( private val ObservableTransitionState.canBeOccluded: Boolean get() = when (this) { - is ObservableTransitionState.Idle -> scene.canBeOccluded + is ObservableTransitionState.Idle -> currentScene.canBeOccluded is ObservableTransitionState.Transition -> fromScene.canBeOccluded && toScene.canBeOccluded } @@ -125,7 +125,9 @@ constructor( Scenes.Communal -> true Scenes.Gone -> true Scenes.Lockscreen -> true + Scenes.NotificationsShade -> false Scenes.QuickSettings -> false + Scenes.QuickSettingsShade -> false Scenes.Shade -> false else -> error("SceneKey \"$this\" doesn't have a mapping for canBeOccluded!") } 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 93cef61d9dae..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,20 +177,48 @@ constructor( toScene: SceneKey, loggingReason: String, transitionKey: TransitionKey? = null, + sceneState: Any? = null, ) { - if (!repository.allSceneKeys().contains(toScene)) { + val currentSceneKey = currentScene.value + if ( + !validateSceneChange( + from = currentSceneKey, + to = toScene, + loggingReason = loggingReason, + ) + ) { return } - check( - toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked - ) { - "Cannot change to the Gone scene while the device is locked. Logging reason for scene" + - " change was: $loggingReason" - } + logger.logSceneChangeRequested( + from = currentSceneKey, + to = toScene, + reason = loggingReason, + isInstant = false, + ) + onSceneAboutToChangeListener.forEach { it.onSceneAboutToChange(toScene, sceneState) } + repository.changeScene(toScene, transitionKey) + } + + /** + * Requests a scene change to the given scene. + * + * The change is instantaneous and not animated; it will be observable in the next frame and + * there will be no transition animation. + */ + fun snapToScene( + toScene: SceneKey, + loggingReason: String, + ) { val currentSceneKey = currentScene.value - if (currentSceneKey == toScene) { + if ( + !validateSceneChange( + from = currentSceneKey, + to = toScene, + loggingReason = loggingReason, + ) + ) { return } @@ -182,9 +226,10 @@ constructor( from = currentSceneKey, to = toScene, reason = loggingReason, + isInstant = true, ) - repository.changeScene(toScene, transitionKey) + repository.snapToScene(toScene) } /** @@ -249,4 +294,32 @@ constructor( ): Boolean { return raw || isRemoteUserInteractionOngoing } + + /** + * Validates that the given scene change is allowed. + * + * Will throw a runtime exception for illegal states (for example, attempting to change to a + * scene that's not part of the current scene framework configuration). + * + * @param from The current scene being transitioned away from + * @param to The desired destination scene to transition to + * @param loggingReason The reason why the transition is requested, for logging purposes + * @return `true` if the scene change is valid; `false` if it shouldn't happen + */ + private fun validateSceneChange( + from: SceneKey, + to: SceneKey, + loggingReason: String, + ): Boolean { + if (!repository.allSceneKeys().contains(to)) { + return false + } + + check(to != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked) { + "Cannot change to the Gone scene while the device is locked. Logging reason for scene" + + " change was: $loggingReason" + } + + return from != to + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt index 1cf1c18749cb..9c2b992c0de6 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt @@ -51,7 +51,7 @@ constructor( private val windowRootViewVisibilityRepository: WindowRootViewVisibilityRepository, private val keyguardRepository: KeyguardRepository, private val headsUpManager: HeadsUpManager, - private val powerInteractor: PowerInteractor, + powerInteractor: PowerInteractor, private val activeNotificationsInteractor: ActiveNotificationsInteractor, sceneInteractorProvider: Provider<SceneInteractor>, ) : CoreStartable { @@ -76,11 +76,18 @@ constructor( .map { state -> when (state) { is ObservableTransitionState.Idle -> - state.scene == Scenes.Shade || state.scene == Scenes.Lockscreen + state.currentScene == Scenes.Shade || + state.currentScene == Scenes.NotificationsShade || + state.currentScene == Scenes.QuickSettingsShade || + state.currentScene == Scenes.Lockscreen is ObservableTransitionState.Transition -> state.toScene == Scenes.Shade || + state.toScene == Scenes.NotificationsShade || + state.toScene == Scenes.QuickSettingsShade || state.toScene == Scenes.Lockscreen || state.fromScene == Scenes.Shade || + state.fromScene == Scenes.NotificationsShade || + state.fromScene == Scenes.QuickSettingsShade || state.fromScene == Scenes.Lockscreen } } 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 d5de28a3ddde..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 @@ -45,9 +45,11 @@ import com.android.systemui.model.updateFlags import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.FalsingManager.FalsingBeliefListener import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.domain.interactor.SceneBackInteractor import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.session.shared.SessionStorage import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.Scenes @@ -116,6 +118,7 @@ constructor( private val shadeInteractor: ShadeInteractor, private val uiEventLogger: UiEventLogger, private val sceneBackInteractor: SceneBackInteractor, + private val shadeSessionStorage: SessionStorage, ) : CoreStartable { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() @@ -132,6 +135,7 @@ constructor( handleBouncerOverscroll() hydrateWindowController() hydrateBackStack() + resetShadeSessions() } else { sceneLogger.logFrameworkEnabled( isEnabled = false, @@ -150,6 +154,20 @@ constructor( } } + private fun resetShadeSessions() { + applicationScope.launch { + sceneBackInteractor.backStack + // We are in a session if either Shade or QuickSettings is on the back stack + .map { backStack -> + backStack.asIterable().any { it == Scenes.Shade || it == Scenes.QuickSettings } + } + .distinctUntilChanged() + // Once a session has ended, clear the session storage. + .filter { inSession -> !inSession } + .collect { shadeSessionStorage.clear() } + } + } + /** Updates the visibility of the scene container. */ private fun hydrateVisibility() { applicationScope.launch { @@ -162,7 +180,7 @@ constructor( sceneInteractor.transitionState.mapNotNull { state -> when (state) { is ObservableTransitionState.Idle -> { - if (state.scene != Scenes.Gone) { + if (state.currentScene != Scenes.Gone) { true to "scene is not Gone" } else { false to "scene is Gone" @@ -216,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", ) } @@ -234,6 +252,7 @@ constructor( when { isAnySimLocked -> { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Bouncer, loggingReason = "Need to authenticate locked SIM card." ) @@ -241,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 " + @@ -249,6 +269,7 @@ constructor( } else -> { switchToScene( + // TODO(b/336581871): add sceneState? targetSceneKey = Scenes.Lockscreen, loggingReason = "All SIM cards unlocked and device still locked" + @@ -272,7 +293,7 @@ constructor( .mapNotNull { deviceUnlockStatus -> val renderedScenes = when (val transitionState = sceneInteractor.transitionState.value) { - is ObservableTransitionState.Idle -> setOf(transitionState.scene) + is ObservableTransitionState.Idle -> setOf(transitionState.currentScene) is ObservableTransitionState.Transition -> setOf( transitionState.fromScene, @@ -347,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", ) @@ -399,7 +421,7 @@ constructor( combine( sceneInteractor.transitionState .mapNotNull { it as? ObservableTransitionState.Idle } - .map { it.scene } + .map { it.currentScene } .distinctUntilChanged(), occlusionInteractor.invisibleDueToOcclusion, ) { sceneKey, invisibleDueToOcclusion -> @@ -424,7 +446,7 @@ constructor( applicationScope.launch { sceneInteractor.transitionState .mapNotNull { transitionState -> - (transitionState as? ObservableTransitionState.Idle)?.scene + (transitionState as? ObservableTransitionState.Idle)?.currentScene } .distinctUntilChanged() .collect { sceneKey -> @@ -524,7 +546,7 @@ constructor( if (isDeviceLocked) { sceneInteractor.transitionState .mapNotNull { it as? ObservableTransitionState.Idle } - .map { it.scene } + .map { it.currentScene } .distinctUntilChanged() .map { sceneKey -> when (sceneKey) { @@ -537,6 +559,7 @@ constructor( Scenes.Lockscreen -> true Scenes.Bouncer -> false Scenes.Shade -> false + Scenes.NotificationsShade -> false else -> null } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt index 5ebdd8698656..9d6720b02052 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt @@ -20,7 +20,6 @@ import com.android.compose.animation.scene.SceneKey import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.SceneFrameworkLog -import java.util.Stack import javax.inject.Inject class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: LogBuffer) { @@ -47,6 +46,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: from: SceneKey, to: SceneKey, reason: String, + isInstant: Boolean, ) { logBuffer.log( tag = TAG, @@ -55,8 +55,17 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: str1 = from.toString() str2 = to.toString() str3 = reason + bool1 = isInstant + }, + messagePrinter = { + buildString { + append("Scene change requested: $str1 → $str2") + if (isInstant) { + append(" (instant)") + } + append(", reason: $str3") + } }, - messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" }, ) } @@ -116,7 +125,7 @@ class SceneLogger @Inject constructor(@SceneFrameworkLog private val logBuffer: ) } - fun logSceneBackStack(backStack: Stack<SceneKey>) { + fun logSceneBackStack(backStack: Iterable<SceneKey>) { logBuffer.log( tag = TAG, level = LogLevel.INFO, diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt index 0e078d5d8064..034da25f1a45 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSource.kt @@ -40,4 +40,11 @@ interface SceneDataSource { toScene: SceneKey, transitionKey: TransitionKey? = null, ) + + /** + * Asks for an instant scene switch to [toScene], without an animated transition of any kind. + */ + fun snapToScene( + toScene: SceneKey, + ) } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt index 2fbcba977a91..43c3635f32fc 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneDataSourceDelegator.kt @@ -56,6 +56,12 @@ class SceneDataSourceDelegator( ) } + override fun snapToScene(toScene: SceneKey) { + delegateMutable.value.snapToScene( + toScene = toScene, + ) + } + /** * Binds the current, dependency injection provided [SceneDataSource] to the given object. * @@ -77,5 +83,7 @@ class SceneDataSourceDelegator( MutableStateFlow(initialSceneKey).asStateFlow() override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) = Unit + + override fun snapToScene(toScene: SceneKey) = Unit } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt index 73fcca8c6b7f..6d139da99345 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scenes.kt @@ -42,11 +42,58 @@ object Scenes { /** The lockscreen is the scene that shows when the device is locked. */ @JvmField val Lockscreen = SceneKey("lockscreen") - /** The quick settings scene shows the quick setting tiles. */ + /** + * The notifications shade scene primarily shows a scrollable list of notifications as an + * overlay UI. + * + * It's used only in the dual shade configuration, where there are two separate shades: one for + * notifications (this scene) and another for [QuickSettingsShade]. + * + * It's not used in the single/accordion configuration (swipe down once to reveal the shade, + * swipe down again the to expand quick settings) or in the "split" shade configuration (on + * large screens or unfolded foldables, where notifications and quick settings are shown + * side-by-side in their own columns). + */ + @JvmField val NotificationsShade = SceneKey("notifications_shade") + + /** + * The quick settings scene shows the quick setting tiles. + * + * This scene is used for single/accordion configuration (swipe down once to reveal the shade, + * swipe down again the to expand quick settings). + * + * For the "split" shade configuration (on large screens or unfolded foldables, where + * notifications and quick settings are shown side-by-side in their own columns), the [Shade] + * scene is used]. + * + * For the dual shade configuration, where there are two separate shades: one for notifications + * and one for quick settings, [NotificationsShade] and [QuickSettingsShade] scenes are used + * respectively. + */ @JvmField val QuickSettings = SceneKey("quick_settings") /** - * The shade is the scene whose primary purpose is to show a scrollable list of notifications. + * The quick settings shade scene shows the quick setting tiles as an overlay UI. + * + * It's used only in the dual shade configuration, where there are two separate shades: one for + * quick settings (this scene) and another for [NotificationsShade]. + * + * It's not used in the single/accordion configuration (swipe down once to reveal the shade, + * swipe down again the to expand quick settings) or in the "split" shade configuration (on + * large screens or unfolded foldables, where notifications and quick settings are shown + * side-by-side in their own columns). + */ + @JvmField val QuickSettingsShade = SceneKey("quick_settings_shade") + + /** + * The shade is the scene that shows a scrollable list of notifications and the minimized + * version of quick settings (AKA "quick quick settings" or "QQS"). + * + * This scene is used for single/accordion configuration (swipe down once to reveal the shade, + * swipe down again the to expand quick settings) and for the "split" shade configuration (on + * large screens or unfolded foldables, where notifications and quick settings are shown + * side-by-side in their own columns). For the dual shade configuration, where there are two + * separate shades: one for notifications and one for quick settings, other scenes are used. */ @JvmField val Shade = SceneKey("shade") } 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 2ef9b731502f..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 @@ -182,12 +182,14 @@ object SceneWindowRootViewBinder { right >= getDisplayWidth(context) -> CutoutLocation.RIGHT else -> CutoutLocation.CENTER } + val viewDisplayCutout = it?.displayCutout DisplayCutout( left, top, right, bottom, location, + viewDisplayCutout, ) } .stateIn(scope, SharingStarted.WhileSubscribed(), DisplayCutout()) @@ -196,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 451fd679969a..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 @@ -51,7 +52,7 @@ constructor( private fun destinationScenes(shadeMode: ShadeMode): Map<UserAction, UserActionResult> { return buildMap { - if (shadeMode == ShadeMode.Single) { + if (shadeMode is ShadeMode.Single) { this[ Swipe( pointerCount = 2, @@ -60,7 +61,21 @@ constructor( )] = UserActionResult(Scenes.QuickSettings) } - this[Swipe(direction = SwipeDirection.Down)] = UserActionResult(Scenes.Shade) + // TODO(b/338577208): Remove this once we add Dual Shade invocation zones. + if (shadeMode is ShadeMode.Dual) { + this[ + Swipe( + pointerCount = 2, + fromSource = Edge.Top, + direction = SwipeDirection.Down, + )] = 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(downSceneKey, downTransitionKey) } } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index ef7829f91723..09c80b09a388 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -124,8 +124,10 @@ constructor( when (toScene) { Scenes.Bouncer -> Classifier.BOUNCER_UNLOCK Scenes.Gone -> Classifier.UNLOCK + Scenes.NotificationsShade -> Classifier.NOTIFICATION_DRAG_DOWN Scenes.Shade -> Classifier.NOTIFICATION_DRAG_DOWN Scenes.QuickSettings -> Classifier.QUICK_SETTINGS + Scenes.QuickSettingsShade -> Classifier.QUICK_SETTINGS else -> null } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt index caa67dff086f..1868b4a29f20 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt @@ -25,7 +25,6 @@ import android.content.Intent import android.os.UserHandle import android.util.Log import android.util.Pair -import android.view.View import android.view.Window import com.android.app.tracing.coroutines.launch import com.android.internal.app.ChooserActivity @@ -41,8 +40,8 @@ constructor( private val intentExecutor: ActionIntentExecutor, @Application private val applicationScope: CoroutineScope, @Assisted val window: Window, - @Assisted val transitionView: View, - @Assisted val onDismiss: (() -> Unit) + @Assisted val viewProxy: ScreenshotViewProxy, + @Assisted val finishDismiss: () -> Unit, ) { var isPendingSharedTransition = false @@ -50,6 +49,7 @@ constructor( fun startSharedTransition(intent: Intent, user: UserHandle, overrideTransition: Boolean) { isPendingSharedTransition = true + viewProxy.fadeForSharedTransition() val windowTransition = createWindowTransition() applicationScope.launch("$TAG#launchIntentAsync") { intentExecutor.launchIntent( @@ -70,7 +70,7 @@ constructor( ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED ) pendingIntent.send(options.toBundle()) - onDismiss.invoke() + viewProxy.requestDismissal(null) } catch (e: PendingIntent.CanceledException) { Log.e(TAG, "Intent cancelled", e) } @@ -89,7 +89,7 @@ constructor( override fun hideSharedElements() { isPendingSharedTransition = false - onDismiss.invoke() + finishDismiss.invoke() } override fun onFinish() {} @@ -98,13 +98,20 @@ constructor( window, callbacks, null, - Pair.create(transitionView, ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME) + Pair.create( + viewProxy.screenshotPreview, + ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME + ) ) } @AssistedFactory interface Factory { - fun create(window: Window, transitionView: View, onDismiss: (() -> Unit)): ActionExecutor + fun create( + window: Window, + viewProxy: ScreenshotViewProxy, + finishDismiss: (() -> Unit) + ): ActionExecutor } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt index a0cef529ecde..15638d3496e9 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt @@ -97,6 +97,7 @@ object ActionIntentCreator { .putExtra(LongScreenshotActivity.EXTRA_SCREENSHOT_USER_HANDLE, owner) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) } private const val EXTRA_EDIT_SOURCE = "edit_source" diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 4eca51d47a36..4ab09185fcdd 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -33,10 +33,11 @@ import android.view.WindowManager import android.view.WindowManagerGlobal import com.android.app.tracing.coroutines.launch import com.android.internal.infra.ServiceConnector -import com.android.systemui.Flags.screenshotActionDismissSystemWindows +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.screenshot.proxy.SystemUiProxy import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.ActivityManagerWrapper import com.android.systemui.statusbar.phone.CentralSurfaces @@ -54,8 +55,8 @@ constructor( private val activityManagerWrapper: ActivityManagerWrapper, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, + private val systemUiProxy: SystemUiProxy, private val displayTracker: DisplayTracker, - private val keyguardController: ScreenshotKeyguardController, ) { /** * Execute the given intent with startActivity while performing operations for screenshot action @@ -83,14 +84,12 @@ constructor( options: ActivityOptions?, transitionCoordinator: ExitTransitionCoordinator?, ) { - if (screenshotActionDismissSystemWindows()) { - keyguardController.dismiss() + if (Flags.fixScreenshotActionDismissSystemWindows()) { activityManagerWrapper.closeSystemWindows( CentralSurfaces.SYSTEM_DIALOG_REASON_SCREENSHOT ) - } else { - dismissKeyguard() } + systemUiProxy.dismissKeyguard() transitionCoordinator?.startExit() if (user == myUserHandle()) { @@ -110,27 +109,6 @@ constructor( } } - private val proxyConnector: ServiceConnector<IScreenshotProxy> = - ServiceConnector.Impl( - context, - Intent(context, ScreenshotProxyService::class.java), - Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, - context.userId, - IScreenshotProxy.Stub::asInterface, - ) - - private suspend fun dismissKeyguard() { - val completion = CompletableDeferred<Unit>() - val onDoneBinder = - object : IOnDoneCallback.Stub() { - override fun onDone(success: Boolean) { - completion.complete(Unit) - } - } - proxyConnector.post { it.dismissKeyguard(onDoneBinder) } - completion.await() - } - private fun getCrossProfileConnector(user: UserHandle): ServiceConnector<ICrossProfileService> = ServiceConnector.Impl<ICrossProfileService>( context, diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt index 4cf18fb482d8..3d024a6a8ccf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -157,6 +157,8 @@ constructor( override fun restoreNonScrollingUi() = view.restoreNonScrollingUi() + override fun fadeForSharedTransition() {} // unused + override fun stopInputListening() = view.stopInputListening() override fun requestFocus() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index 07e143a34319..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 @@ -87,7 +90,8 @@ constructor( AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_edit), context.resources.getString(R.string.screenshot_edit_label), context.resources.getString(R.string.screenshot_edit_description), - ) + ), + showDuringEntrance = true, ) { debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" } uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString) @@ -105,7 +109,8 @@ constructor( AppCompatResources.getDrawable(context, R.drawable.ic_screenshot_share), context.resources.getString(R.string.screenshot_share_label), context.resources.getString(R.string.screenshot_share_description), - ) + ), + showDuringEntrance = true, ) { debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" } uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString) @@ -120,17 +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), - ) - ) { - 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 494fc9b8c683..9ad6d0faea88 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -47,7 +47,6 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Insets; import android.graphics.Rect; -import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Process; import android.os.UserHandle; @@ -208,8 +207,7 @@ public class ScreenshotController { @Nullable private final ScreenshotSoundController mScreenshotSoundController; private final PhoneWindow mWindow; - private final DisplayManager mDisplayManager; - private final int mDisplayId; + private final Display mDisplay; private final ScrollCaptureExecutor mScrollCaptureExecutor; private final ScreenshotNotificationSmartActionsProvider mScreenshotNotificationSmartActionsProvider; @@ -249,7 +247,6 @@ public class ScreenshotController { @AssistedInject ScreenshotController( Context context, - DisplayManager displayManager, WindowManager windowManager, FeatureFlags flags, ScreenshotViewProxy.Factory viewProxyFactory, @@ -271,12 +268,13 @@ public class ScreenshotController { AssistContentRequester assistContentRequester, MessageContainerController messageContainerController, Provider<ScreenshotSoundController> screenshotSoundController, - @Assisted int displayId, + @Assisted Display display, @Assisted boolean showUIOnExternalDisplay ) { mScreenshotSmartActions = screenshotSmartActions; mActionsProviderFactory = actionsProviderFactory; - mNotificationsController = screenshotNotificationsControllerFactory.create(displayId); + mNotificationsController = screenshotNotificationsControllerFactory.create( + display.getDisplayId()); mUiEventLogger = uiEventLogger; mImageExporter = imageExporter; mImageCapture = imageCapture; @@ -290,11 +288,9 @@ public class ScreenshotController { mScreenshotHandler = timeoutHandler; mScreenshotHandler.setDefaultTimeoutMillis(SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS); - - mDisplayId = displayId; - mDisplayManager = displayManager; + mDisplay = display; mWindowManager = windowManager; - final Context displayContext = context.createDisplayContext(getDisplay()); + final Context displayContext = context.createDisplayContext(display); mContext = (WindowContext) displayContext.createWindowContext(TYPE_SCREENSHOT, null); mFlags = flags; mActionIntentExecutor = actionIntentExecutor; @@ -302,7 +298,7 @@ public class ScreenshotController { mMessageContainerController = messageContainerController; mAssistContentRequester = assistContentRequester; - mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId); + mViewProxy = viewProxyFactory.getProxy(mContext, mDisplay.getDisplayId()); mScreenshotHandler.setOnTimeoutRunnable(() -> { if (DEBUG_UI) { @@ -321,14 +317,14 @@ public class ScreenshotController { mConfigChanges.applyNewConfig(context.getResources()); reloadAssets(); - mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy.getScreenshotPreview(), + mActionExecutor = actionExecutorFactory.create(mWindow, mViewProxy, () -> { - requestDismissal(null); + finishDismiss(); return Unit.INSTANCE; }); // Sound is only reproduced from the controller of the default display. - if (displayId == Display.DEFAULT_DISPLAY) { + if (mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY) { mScreenshotSoundController = screenshotSoundController.get(); } else { mScreenshotSoundController = null; @@ -356,7 +352,7 @@ public class ScreenshotController { if (screenshot.getType() == WindowManager.TAKE_SCREENSHOT_FULLSCREEN && screenshot.getBitmap() == null) { Rect bounds = getFullScreenRect(); - screenshot.setBitmap(mImageCapture.captureDisplay(mDisplayId, bounds)); + screenshot.setBitmap(mImageCapture.captureDisplay(mDisplay.getDisplayId(), bounds)); screenshot.setScreenBounds(bounds); } @@ -459,7 +455,7 @@ public class ScreenshotController { } private boolean shouldShowUi() { - return mDisplayId == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay; + return mDisplay.getDisplayId() == Display.DEFAULT_DISPLAY || mShowUIOnExternalDisplay; } void prepareViewForNewScreenshot(@NonNull ScreenshotData screenshot, String oldPackageName) { @@ -590,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( @@ -618,14 +618,16 @@ public class ScreenshotController { private void requestScrollCapture(UserHandle owner) { mScrollCaptureExecutor.requestScrollCapture( - mDisplayId, + mDisplay.getDisplayId(), mWindow.getDecorView().getWindowToken(), (response) -> { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, response.getPackageName()); - if (screenshotShelfUi2() && mActionsProvider != null) { - mActionsProvider.onScrollChipReady( - () -> onScrollButtonClicked(owner, response)); + if (screenshotShelfUi2()) { + if (mActionsProvider != null) { + mActionsProvider.onScrollChipReady( + () -> onScrollButtonClicked(owner, response)); + } } else { mViewProxy.showScrollChip(response.getPackageName(), () -> onScrollButtonClicked(owner, response)); @@ -641,7 +643,8 @@ public class ScreenshotController { } mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0, response.getPackageName()); - Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplayId, getFullScreenRect()); + Bitmap newScreenshot = mImageCapture.captureDisplay(mDisplay.getDisplayId(), + getFullScreenRect()); if (newScreenshot == null) { Log.e(TAG, "Failed to capture current screenshot for scroll transition!"); return; @@ -656,9 +659,7 @@ public class ScreenshotController { () -> { final Intent intent = ActionIntentCreator.INSTANCE.createLongScreenshotIntent( owner, mContext); - mActionIntentExecutor.launchIntentAsync(intent, owner, true, - ActivityOptions.makeCustomAnimation(mContext, 0, 0), null); - + mActionIntentExecutor.launchIntentAsync(intent, owner, true, null, null); }, mViewProxy::restoreNonScrollingUi, mViewProxy::startLongScreenshotTransition); @@ -819,7 +820,8 @@ public class ScreenshotController { private void saveScreenshotInBackground( ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) { ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor, - requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), mDisplayId); + requestId, screenshot.getBitmap(), screenshot.getUserOrDefault(), + mDisplay.getDisplayId()); future.addListener(() -> { try { ImageExporter.Result result = future.get(); @@ -861,7 +863,7 @@ public class ScreenshotController { data.mActionsReadyListener = actionsReadyListener; data.mQuickShareActionsReadyListener = quickShareActionsReadyListener; data.owner = owner; - data.displayId = mDisplayId; + data.displayId = mDisplay.getDisplayId(); if (mSaveInBgTask != null) { // just log success/failure for the pre-existing screenshot @@ -986,13 +988,9 @@ public class ScreenshotController { } } - private Display getDisplay() { - return mDisplayManager.getDisplay(mDisplayId); - } - private Rect getFullScreenRect() { DisplayMetrics displayMetrics = new DisplayMetrics(); - getDisplay().getRealMetrics(displayMetrics); + mDisplay.getRealMetrics(displayMetrics); return new Rect(0, 0, displayMetrics.widthPixels, displayMetrics.heightPixels); } @@ -1026,12 +1024,12 @@ public class ScreenshotController { @AssistedFactory public interface Factory { /** - * Creates an instance of the controller for that specific displayId. + * Creates an instance of the controller for that specific display. * - * @param displayId: display to capture - * @param showUIOnExternalDisplay: Whether the UI should be shown if this is an external - * display. + * @param display display to capture + * @param showUIOnExternalDisplay Whether the UI should be shown if this is an external + * display. */ - ScreenshotController create(int displayId, boolean showUIOnExternalDisplay); + ScreenshotController create(Display display, boolean showUIOnExternalDisplay); } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt deleted file mode 100644 index 7696bbe3763e..000000000000 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotKeyguardController.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.screenshot - -import android.content.Context -import android.content.Intent -import com.android.internal.infra.ServiceConnector -import javax.inject.Inject -import kotlinx.coroutines.CompletableDeferred - -open class ScreenshotKeyguardController @Inject constructor(context: Context) { - private val proxyConnector: ServiceConnector<IScreenshotProxy> = - ServiceConnector.Impl( - context, - Intent(context, ScreenshotProxyService::class.java), - Context.BIND_AUTO_CREATE or Context.BIND_WAIVE_PRIORITY or Context.BIND_NOT_VISIBLE, - context.userId, - IScreenshotProxy.Stub::asInterface - ) - - suspend fun dismiss() { - val completion = CompletableDeferred<Unit>() - val onDoneBinder = - object : IOnDoneCallback.Stub() { - override fun onDone(success: Boolean) { - completion.complete(Unit) - } - } - proxyConnector.post { it.dismissKeyguard(onDoneBinder) } - completion.await() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index 9b754f3271a7..3ac070a28b2b 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt @@ -31,6 +31,7 @@ import android.view.WindowInsets import android.view.WindowManager import android.window.OnBackInvokedCallback import android.window.OnBackInvokedDispatcher +import androidx.appcompat.content.res.AppCompatResources import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.internal.logging.UiEventLogger @@ -45,6 +46,7 @@ import com.android.systemui.screenshot.scroll.ScrollCaptureController import com.android.systemui.screenshot.ui.ScreenshotAnimationController import com.android.systemui.screenshot.ui.ScreenshotShelfView import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder +import com.android.systemui.screenshot.ui.viewmodel.AnimationState import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -57,6 +59,7 @@ constructor( private val logger: UiEventLogger, private val viewModel: ScreenshotViewModel, private val windowManager: WindowManager, + shelfViewBinder: ScreenshotShelfViewBinder, private val thumbnailObserver: ThumbnailObserver, @Assisted private val context: Context, @Assisted private val displayId: Int @@ -68,7 +71,17 @@ constructor( override var callbacks: ScreenshotView.ScreenshotViewCallback? = null override var screenshot: ScreenshotData? = null set(value) { - viewModel.setScreenshotBitmap(value?.bitmap) + value?.let { + viewModel.setScreenshotBitmap(it.bitmap) + val badgeBg = + AppCompatResources.getDrawable(context, R.drawable.overlay_badge_background) + val user = it.userHandle + if (badgeBg != null && user != null) { + viewModel.setScreenshotBadge( + context.packageManager.getUserBadgedIcon(badgeBg, user) + ) + } + } field = value } @@ -77,15 +90,16 @@ constructor( override var isDismissing = false override var isPendingSharedTransition = false - private val animationController = ScreenshotAnimationController(view) + private val animationController = ScreenshotAnimationController(view, viewModel) init { - ScreenshotShelfViewBinder.bind( + shelfViewBinder.bind( view, viewModel, + animationController, LayoutInflater.from(context), onDismissalRequested = { event, velocity -> requestDismissal(event, velocity) }, - onDismissalCancelled = { animationController.getSwipeReturnAnimation().start() } + onUserInteraction = { callbacks?.onUserInteraction() } ) view.updateInsets(windowManager.currentWindowMetrics.windowInsets) addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) } @@ -103,7 +117,7 @@ constructor( } screenshotPreview = view.screenshotPreview thumbnailObserver.setViews( - view.screenshotPreview, + view.blurredScreenshotPreview, view.requireViewById(R.id.screenshot_preview_border) ) } @@ -119,12 +133,19 @@ constructor( override fun updateOrientation(insets: WindowInsets) {} override fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator { - val entrance = animationController.getEntranceAnimation(screenRect, showFlash) - entrance.doOnStart { thumbnailObserver.onEntranceStarted() } + val entrance = + animationController.getEntranceAnimation(screenRect, showFlash) { + viewModel.setAnimationState(AnimationState.ENTRANCE_REVEAL) + } + entrance.doOnStart { + thumbnailObserver.onEntranceStarted() + viewModel.setAnimationState(AnimationState.ENTRANCE_STARTED) + } entrance.doOnEnd { // reset the timeout when animation finishes callbacks?.onUserInteraction() thumbnailObserver.onEntranceComplete() + viewModel.setAnimationState(AnimationState.ENTRANCE_COMPLETE) } return entrance } @@ -167,24 +188,53 @@ constructor( override fun prepareScrollingTransition( response: ScrollCaptureResponse, - screenBitmap: Bitmap, + screenBitmap: Bitmap, // unused newScreenshot: Bitmap, screenshotTakenInPortrait: Boolean, onTransitionPrepared: Runnable, ) { - onTransitionPrepared.run() + viewModel.setScrollingScrimBitmap(newScreenshot) + viewModel.setScrollableRect(scrollableAreaOnScreen(response)) + animationController.fadeForLongScreenshotTransition() + view.post { onTransitionPrepared.run() } + } + + private fun scrollableAreaOnScreen(response: ScrollCaptureResponse): Rect { + val r = Rect(response.boundsInWindow) + val windowInScreen = response.windowBounds + r.offset(windowInScreen?.left ?: 0, windowInScreen?.top ?: 0) + r.intersect( + Rect( + 0, + 0, + context.resources.displayMetrics.widthPixels, + context.resources.displayMetrics.heightPixels + ) + ) + return r } override fun startLongScreenshotTransition( transitionDestination: Rect, onTransitionEnd: Runnable, - longScreenshot: ScrollCaptureController.LongScreenshot + longScreenshot: ScrollCaptureController.LongScreenshot, ) { - onTransitionEnd.run() - callbacks?.onDismiss() + val transitionAnimation = + animationController.runLongScreenshotTransition( + transitionDestination, + longScreenshot, + onTransitionEnd + ) + transitionAnimation.doOnEnd { callbacks?.onDismiss() } + transitionAnimation.start() } - override fun restoreNonScrollingUi() {} + override fun restoreNonScrollingUi() { + viewModel.setScrollableRect(null) + viewModel.setScrollingScrimBitmap(null) + animationController.restoreUI() + callbacks?.onUserInteraction() // reset the timeout + } override fun stopInputListening() {} @@ -207,6 +257,10 @@ constructor( ) } + override fun fadeForSharedTransition() { + animationController.fadeForSharedTransition() + } + private fun addPredictiveBackListener(onDismissRequested: (ScreenshotEvent) -> Unit) { val onBackInvokedCallback = OnBackInvokedCallback { debugLog(DEBUG_INPUT) { "Predictive Back callback dispatched" } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt index a4069d11f8fb..df93a5e56c22 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -63,6 +63,7 @@ interface ScreenshotViewProxy { longScreenshot: ScrollCaptureController.LongScreenshot ) fun restoreNonScrollingUi() + fun fadeForSharedTransition() fun stopInputListening() fun requestFocus() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index e56a4f45d7aa..40d709d0ab25 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -68,11 +68,13 @@ constructor( onSaved: (Uri?) -> Unit, requestCallback: RequestCallback ) { - val displayIds = getDisplaysToScreenshot(screenshotRequest.type) + val displays = getDisplaysToScreenshot(screenshotRequest.type) val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback) - displayIds.forEach { displayId: Int -> + displays.forEach { display -> + val displayId = display.displayId Log.d(TAG, "Executing screenshot for display $displayId") dispatchToController( + display = display, rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId), onSaved = if (displayId == Display.DEFAULT_DISPLAY) { @@ -85,6 +87,7 @@ constructor( /** All logging should be triggered only by this method. */ private suspend fun dispatchToController( + display: Display, rawScreenshotData: ScreenshotData, onSaved: (Uri?) -> Unit, callback: RequestCallback @@ -104,8 +107,7 @@ constructor( logScreenshotRequested(screenshotData) Log.d(TAG, "Screenshot request: $screenshotData") try { - getScreenshotController(screenshotData.displayId) - .handleScreenshot(screenshotData, onSaved, callback) + getScreenshotController(display).handleScreenshot(screenshotData, onSaved, callback) } catch (e: IllegalStateException) { Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e) onFailedScreenshotRequest(screenshotData, callback) @@ -135,12 +137,13 @@ constructor( callback.reportError() } - private suspend fun getDisplaysToScreenshot(requestType: Int): List<Int> { + private suspend fun getDisplaysToScreenshot(requestType: Int): List<Display> { + val allDisplays = displays.first() return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) { // If this is a provided image, let's show the UI on the default display only. - listOf(Display.DEFAULT_DISPLAY) + allDisplays.filter { it.displayId == Display.DEFAULT_DISPLAY } } else { - displays.first().filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId } + allDisplays.filter { it.type in ALLOWED_DISPLAY_TYPES } } } @@ -170,9 +173,9 @@ constructor( screenshotControllers.clear() } - private fun getScreenshotController(id: Int): ScreenshotController { - return screenshotControllers.computeIfAbsent(id) { - screenshotControllerFactory.create(id, /* showUIOnExternalDisplay= */ false) + private fun getScreenshotController(display: Display): ScreenshotController { + return screenshotControllers.computeIfAbsent(display.displayId) { + screenshotControllerFactory.create(display, /* showUIOnExternalDisplay= */ false) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java index 5e561cfb14a1..ee1944e0b5cf 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/CropView.java @@ -45,6 +45,7 @@ import androidx.customview.widget.ExploreByTouchHelper; import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import com.android.internal.graphics.ColorUtils; +import com.android.systemui.Flags; import com.android.systemui.res.R; import java.util.List; @@ -378,8 +379,14 @@ public class CropView extends View { upper = 1; break; } - Log.i(TAG, "getAllowedValues: " + boundary + ", " - + "result=[lower=" + lower + ", upper=" + upper + "]"); + if (lower >= upper) { + Log.wtf(TAG, "getAllowedValues computed an invalid range " + + "[" + lower + ", " + upper + "]"); + if (Flags.screenshotScrollCropViewCrashFix()) { + lower = Math.min(lower, upper); + upper = lower; + } + } return new Range<>(lower, upper); } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt index 4eceb176fcd2..a4906c12b487 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/ScreenshotAnimationController.kt @@ -20,6 +20,10 @@ import android.animation.Animator import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator +import android.content.res.ColorStateList +import android.graphics.BlendMode +import android.graphics.Color +import android.graphics.Matrix import android.graphics.PointF import android.graphics.Rect import android.util.MathUtils @@ -29,13 +33,21 @@ import android.widget.ImageView import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import com.android.systemui.res.R +import com.android.systemui.screenshot.scroll.ScrollCaptureController +import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import kotlin.math.abs import kotlin.math.max import kotlin.math.sign -class ScreenshotAnimationController(private val view: ScreenshotShelfView) { +class ScreenshotAnimationController( + private val view: ScreenshotShelfView, + private val viewModel: ScreenshotViewModel +) { private var animator: Animator? = null private val screenshotPreview = view.requireViewById<ImageView>(R.id.screenshot_preview) + private val scrollingScrim = view.requireViewById<ImageView>((R.id.screenshot_scrolling_scrim)) + private val scrollTransitionPreview = + view.requireViewById<ImageView>(R.id.screenshot_scrollable_preview) private val flashView = view.requireViewById<View>(R.id.screenshot_flash) private val actionContainer = view.requireViewById<View>(R.id.actions_container_background) private val fastOutSlowIn = @@ -43,12 +55,23 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { private val staticUI = listOf<View>( view.requireViewById(R.id.screenshot_preview_border), - view.requireViewById(R.id.actions_container_background), view.requireViewById(R.id.screenshot_badge), view.requireViewById(R.id.screenshot_dismiss_button) ) + private val fadeUI = + listOf<View>( + view.requireViewById(R.id.screenshot_preview_border), + view.requireViewById(R.id.actions_container_background), + view.requireViewById(R.id.screenshot_badge), + view.requireViewById(R.id.screenshot_dismiss_button), + view.requireViewById(R.id.screenshot_message_container), + ) - fun getEntranceAnimation(bounds: Rect, showFlash: Boolean): Animator { + fun getEntranceAnimation( + bounds: Rect, + showFlash: Boolean, + onRevealMilestone: () -> Unit + ): Animator { val entranceAnimation = AnimatorSet() val previewAnimator = getPreviewAnimator(bounds) @@ -71,6 +94,20 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { entranceAnimation.doOnStart { screenshotPreview.visibility = View.INVISIBLE } } + val actionsAnimator = getActionsAnimator() + entranceAnimation.play(actionsAnimator).with(previewAnimator) + + // This isn't actually animating anything but is basically a timer for the first 200ms of + // the entrance animation. Using an animator here ensures that this is scaled if we change + // animator duration scales. + val revealMilestoneAnimator = + ValueAnimator.ofFloat(0f).apply { + duration = 0 + startDelay = ACTION_REVEAL_DELAY_MS + doOnEnd { onRevealMilestone() } + } + entranceAnimation.play(revealMilestoneAnimator).with(actionsAnimator) + val fadeInAnimator = ValueAnimator.ofFloat(0f, 1f) fadeInAnimator.addUpdateListener { for (child in staticUI) { @@ -79,15 +116,108 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { } entranceAnimation.play(fadeInAnimator).after(previewAnimator) entranceAnimation.doOnStart { + viewModel.setIsAnimating(true) for (child in staticUI) { child.alpha = 0f } } + entranceAnimation.doOnEnd { viewModel.setIsAnimating(false) } this.animator = entranceAnimation return entranceAnimation } + fun fadeForSharedTransition() { + animator?.cancel() + val fadeAnimator = ValueAnimator.ofFloat(1f, 0f) + fadeAnimator.addUpdateListener { + for (view in fadeUI) { + view.alpha = it.animatedValue as Float + } + } + animator = fadeAnimator + fadeAnimator.start() + } + + fun runLongScreenshotTransition( + destRect: Rect, + longScreenshot: ScrollCaptureController.LongScreenshot, + onTransitionEnd: Runnable + ): Animator { + val animSet = AnimatorSet() + + val scrimAnim = ValueAnimator.ofFloat(0f, 1f) + scrimAnim.addUpdateListener { animation: ValueAnimator -> + scrollingScrim.setAlpha(1 - animation.animatedFraction) + } + scrollTransitionPreview.visibility = View.VISIBLE + if (true) { + scrollTransitionPreview.setImageBitmap(longScreenshot.toBitmap()) + val startX: Float = scrollTransitionPreview.x + val startY: Float = scrollTransitionPreview.y + val locInScreen: IntArray = scrollTransitionPreview.getLocationOnScreen() + destRect.offset(startX.toInt() - locInScreen[0], startY.toInt() - locInScreen[1]) + scrollTransitionPreview.pivotX = 0f + scrollTransitionPreview.pivotY = 0f + scrollTransitionPreview.setAlpha(1f) + val currentScale: Float = scrollTransitionPreview.width / longScreenshot.width.toFloat() + val matrix = Matrix() + matrix.setScale(currentScale, currentScale) + matrix.postTranslate( + longScreenshot.left * currentScale, + longScreenshot.top * currentScale + ) + scrollTransitionPreview.setImageMatrix(matrix) + val destinationScale: Float = destRect.width() / scrollTransitionPreview.width.toFloat() + val previewAnim = ValueAnimator.ofFloat(0f, 1f) + previewAnim.addUpdateListener { animation: ValueAnimator -> + val t = animation.animatedFraction + val currScale = MathUtils.lerp(1f, destinationScale, t) + scrollTransitionPreview.scaleX = currScale + scrollTransitionPreview.scaleY = currScale + scrollTransitionPreview.x = MathUtils.lerp(startX, destRect.left.toFloat(), t) + scrollTransitionPreview.y = MathUtils.lerp(startY, destRect.top.toFloat(), t) + } + val previewFadeAnim = ValueAnimator.ofFloat(1f, 0f) + previewFadeAnim.addUpdateListener { animation: ValueAnimator -> + scrollTransitionPreview.setAlpha(1 - animation.animatedFraction) + } + previewAnim.doOnEnd { onTransitionEnd.run() } + animSet.play(previewAnim).with(scrimAnim).before(previewFadeAnim) + } else { + // if we switched orientations between the original screenshot and the long screenshot + // capture, just fade out the scrim instead of running the preview animation + scrimAnim.doOnEnd { onTransitionEnd.run() } + animSet.play(scrimAnim) + } + animator = animSet + return animSet + } + + fun fadeForLongScreenshotTransition() { + scrollingScrim.imageTintBlendMode = BlendMode.SRC_ATOP + val anim = ValueAnimator.ofFloat(0f, .3f) + anim.addUpdateListener { + scrollingScrim.setImageTintList( + ColorStateList.valueOf(Color.argb(it.animatedValue as Float, 0f, 0f, 0f)) + ) + } + for (view in fadeUI) { + view.alpha = 0f + } + screenshotPreview.alpha = 0f + anim.setDuration(200) + anim.start() + } + + fun restoreUI() { + animator?.cancel() + for (view in fadeUI) { + view.alpha = 1f + } + screenshotPreview.alpha = 1f + } + fun getSwipeReturnAnimation(): Animator { animator?.cancel() val animator = ValueAnimator.ofFloat(view.translationX, 0f) @@ -97,6 +227,7 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { } fun getSwipeDismissAnimation(requestedVelocity: Float?): Animator { + animator?.cancel() val velocity = getAdjustedVelocity(requestedVelocity) val screenWidth = view.resources.displayMetrics.widthPixels // translation at which point the visible UI is fully off the screen (in the direction @@ -114,6 +245,8 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { view.alpha = 1f - it.animatedFraction } animator.duration = ((abs(distance / velocity))).toLong() + animator.doOnStart { viewModel.setIsAnimating(true) } + animator.doOnEnd { viewModel.setIsAnimating(false) } this.animator = animator return animator @@ -123,6 +256,20 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { animator?.cancel() } + private fun getActionsAnimator(): Animator { + val startingOffset = view.height - actionContainer.top + val actionsYAnimator = + ValueAnimator.ofFloat(startingOffset.toFloat(), 0f).apply { + duration = PREVIEW_Y_ANIMATION_DURATION_MS + interpolator = fastOutSlowIn + } + actionsYAnimator.addUpdateListener { + actionContainer.translationY = it.animatedValue as Float + } + actionContainer.translationY = startingOffset.toFloat() + return actionsYAnimator + } + private fun getPreviewAnimator(bounds: Rect): Animator { val targetPosition = Rect() screenshotPreview.getHitRect(targetPosition) @@ -183,5 +330,6 @@ class ScreenshotAnimationController(private val view: ScreenshotShelfView) { private const val FLASH_OUT_DURATION_MS: Long = 217 private const val PREVIEW_X_ANIMATION_DURATION_MS: Long = 234 private const val PREVIEW_Y_ANIMATION_DURATION_MS: Long = 500 + private const val ACTION_REVEAL_DELAY_MS: Long = 200 } } 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 4437bf533353..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,27 +32,75 @@ 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) { lateinit var screenshotPreview: ImageView + lateinit var blurredScreenshotPreview: ImageView 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. isFocusableInTouchMode = true screenshotPreview = requireViewById(R.id.screenshot_preview) + 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 { @@ -75,35 +125,60 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) : fun updateInsets(insets: WindowInsets) { val orientation = mContext.resources.configuration.orientation val inPortrait = orientation == Configuration.ORIENTATION_PORTRAIT - val p = screenshotStatic.layoutParams as LayoutParams 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) { - p.setMargins(0, 0, 0, navBarInsets.bottom) + screenshotStatic.setPadding(0, 0, 0, navBarInsets.bottom) } else { val waterfall = cutout.waterfallInsets if (inPortrait) { - p.setMargins( + 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 { - p.setMargins( - max(cutout.safeInsetLeft.toDouble(), waterfall.left.toDouble()).toInt(), + screenshotStatic.setPadding( + 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, + ) ) } } - screenshotStatic.layoutParams = p - screenshotStatic.requestLayout() + } + + // 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 { @@ -128,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/TransitioningIconDrawable.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/TransitioningIconDrawable.kt new file mode 100644 index 000000000000..0bc280c6c1e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/TransitioningIconDrawable.kt @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot.ui + +import android.animation.ValueAnimator +import android.content.res.ColorStateList +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.drawable.Drawable +import androidx.core.animation.doOnEnd +import java.util.Objects + +/** */ +class TransitioningIconDrawable : Drawable() { + // The drawable for the current icon of this view. During icon transitions, this is the one + // being animated out. + private var drawable: Drawable? = null + + // The incoming new icon. Only populated during transition animations (when drawable is also + // non-null). + private var enteringDrawable: Drawable? = null + private var colorFilter: ColorFilter? = null + private var tint: ColorStateList? = null + private var alpha = 255 + + private var transitionAnimator = + ValueAnimator.ofFloat(0f, 1f).also { it.doOnEnd { onTransitionComplete() } } + + /** + * Set the drawable to be displayed, potentially animating the transition from one icon to the + * next. + */ + fun setIcon(incomingDrawable: Drawable?) { + if (Objects.equals(drawable, incomingDrawable) && !transitionAnimator.isRunning) { + return + } + + incomingDrawable?.colorFilter = colorFilter + incomingDrawable?.setTintList(tint) + + if (drawable == null) { + // No existing icon drawn, just show the new one without a transition + drawable = incomingDrawable + invalidateSelf() + return + } + + if (enteringDrawable != null) { + // There's already an entrance animation happening, just update the entering icon, not + // maintaining a queue or anything. + enteringDrawable = incomingDrawable + return + } + + // There was already an icon, need to animate between icons. + enteringDrawable = incomingDrawable + transitionAnimator.setCurrentFraction(0f) + transitionAnimator.start() + invalidateSelf() + } + + override fun draw(canvas: Canvas) { + // Scale the old one down, scale the new one up. + drawable?.let { + val scale = + if (transitionAnimator.isRunning) { + 1f - transitionAnimator.animatedFraction + } else { + 1f + } + drawScaledDrawable(it, canvas, scale) + } + enteringDrawable?.let { + val scale = transitionAnimator.animatedFraction + drawScaledDrawable(it, canvas, scale) + } + + if (transitionAnimator.isRunning) { + invalidateSelf() + } + } + + private fun drawScaledDrawable(drawable: Drawable, canvas: Canvas, scale: Float) { + drawable.bounds = getBounds() + canvas.save() + canvas.scale( + scale, + scale, + (drawable.intrinsicWidth / 2).toFloat(), + (drawable.intrinsicHeight / 2).toFloat() + ) + drawable.draw(canvas) + canvas.restore() + } + + private fun onTransitionComplete() { + drawable = enteringDrawable + enteringDrawable = null + invalidateSelf() + } + + override fun setTintList(tint: ColorStateList?) { + super.setTintList(tint) + drawable?.setTintList(tint) + enteringDrawable?.setTintList(tint) + this.tint = tint + } + + override fun setAlpha(alpha: Int) { + this.alpha = alpha + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + this.colorFilter = colorFilter + drawable?.colorFilter = colorFilter + enteringDrawable?.colorFilter = colorFilter + } + + override fun getOpacity(): Int = alpha +} 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 3c5a0ec107f8..36aa39fa9fe4 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 @@ -21,15 +21,35 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import com.android.systemui.res.R +import com.android.systemui.screenshot.ui.TransitioningIconDrawable import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel +import javax.inject.Inject -object ActionButtonViewBinder { +class ActionButtonViewBinder @Inject constructor() { /** Binds the given view to the given view-model */ fun bind(view: View, viewModel: ActionButtonViewModel) { val iconView = view.requireViewById<ImageView>(R.id.overlay_action_chip_icon) val textView = view.requireViewById<TextView>(R.id.overlay_action_chip_text) + if (iconView.drawable == null) { + iconView.setImageDrawable(TransitioningIconDrawable()) + } + val drawable = iconView.drawable as? TransitioningIconDrawable + // Note we never re-bind a view to a different ActionButtonViewModel, different view + // models would remove/create separate views. + drawable?.setIcon(viewModel.appearance.icon) iconView.setImageDrawable(viewModel.appearance.icon) + if (!viewModel.appearance.tint) { + iconView.setImageTintList(null) + } 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 734a530efea7..442b3873be4d 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 @@ -16,7 +16,11 @@ package com.android.systemui.screenshot.ui.binder +import android.content.res.Configuration import android.graphics.Bitmap +import android.graphics.Matrix +import android.graphics.Rect +import android.util.LayoutDirection import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -29,20 +33,27 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.screenshot.ScreenshotEvent +import com.android.systemui.screenshot.ui.ScreenshotAnimationController import com.android.systemui.screenshot.ui.ScreenshotShelfView import com.android.systemui.screenshot.ui.SwipeGestureListener +import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel +import com.android.systemui.screenshot.ui.viewmodel.AnimationState import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import com.android.systemui.util.children +import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -object ScreenshotShelfViewBinder { +class ScreenshotShelfViewBinder +@Inject +constructor(private val buttonViewBinder: ActionButtonViewBinder) { fun bind( view: ScreenshotShelfView, viewModel: ScreenshotViewModel, + animationController: ScreenshotAnimationController, layoutInflater: LayoutInflater, onDismissalRequested: (event: ScreenshotEvent, velocity: Float?) -> Unit, - onDismissalCancelled: () -> Unit, + onUserInteraction: () -> Unit ) { val swipeGestureListener = SwipeGestureListener( @@ -50,19 +61,25 @@ object ScreenshotShelfViewBinder { onDismiss = { onDismissalRequested(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, it) }, - onCancel = onDismissalCancelled + onCancel = { animationController.getSwipeReturnAnimation().start() } ) 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) val previewBorder = view.requireViewById<View>(R.id.screenshot_preview_border) previewView.clipToOutline = true + previewViewBlur.clipToOutline = true val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions) val dismissButton = view.requireViewById<View>(R.id.screenshot_dismiss_button) dismissButton.visibility = if (viewModel.showDismissButton) View.VISIBLE else View.GONE dismissButton.setOnClickListener { onDismissalRequested(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL, null) } + val scrollingScrim: ImageView = view.requireViewById(R.id.screenshot_scrolling_scrim) + val scrollablePreview: ImageView = view.requireViewById(R.id.screenshot_scrollable_preview) + val badgeView = view.requireViewById<ImageView>(R.id.screenshot_badge) // use immediate dispatcher to ensure screenshot bitmap is set before animation view.repeatWhenAttached(Dispatchers.Main.immediate) { @@ -72,6 +89,7 @@ object ScreenshotShelfViewBinder { viewModel.preview.collect { bitmap -> if (bitmap != null) { setScreenshotBitmap(previewView, bitmap) + setScreenshotBitmap(previewViewBlur, bitmap) previewView.visibility = View.VISIBLE previewBorder.visibility = View.VISIBLE } else { @@ -81,53 +99,115 @@ object ScreenshotShelfViewBinder { } } launch { + viewModel.scrollingScrim.collect { bitmap -> + if (bitmap != null) { + scrollingScrim.setImageBitmap(bitmap) + scrollingScrim.visibility = View.VISIBLE + } else { + scrollingScrim.visibility = View.GONE + } + } + } + launch { + viewModel.scrollableRect.collect { rect -> + if (rect != null) { + setScrollablePreview( + scrollablePreview, + viewModel.preview.value, + rect + ) + } else { + scrollablePreview.visibility = View.GONE + } + } + } + launch { + viewModel.badge.collect { badge -> + badgeView.setImageDrawable(badge) + badgeView.visibility = if (badge != null) View.VISIBLE else View.GONE + } + } + launch { viewModel.previewAction.collect { onClick -> previewView.setOnClickListener { onClick?.invoke() } } } launch { + viewModel.isAnimating.collect { isAnimating -> + previewView.isClickable = !isAnimating + for (child in actionsContainer.children) { + child.isClickable = !isAnimating + } + } + } + launch { viewModel.actions.collect { actions -> - val visibleActions = actions.filter { it.visible } + updateActions( + actions, + viewModel.animationState.value, + view, + layoutInflater + ) + } + } + launch { + viewModel.animationState.collect { animationState -> + updateActions( + viewModel.actions.value, + animationState, + view, + layoutInflater + ) + } + } + } + } + } + } - if (visibleActions.isNotEmpty()) { - view - .requireViewById<View>(R.id.actions_container_background) - .visibility = View.VISIBLE - } + private fun updateActions( + actions: List<ActionButtonViewModel>, + animationState: AnimationState, + view: ScreenshotShelfView, + layoutInflater: LayoutInflater + ) { + val actionsContainer: LinearLayout = view.requireViewById(R.id.screenshot_actions) + val visibleActions = + actions.filter { + it.visible && + (animationState == AnimationState.ENTRANCE_COMPLETE || + animationState == AnimationState.ENTRANCE_REVEAL || + it.showDuringEntrance) + } - // Remove any buttons not in the new list, then do another pass to add - // any new actions and update any that are already there. - // This assumes that actions can never change order and that each action - // ID is unique. - val newIds = visibleActions.map { it.id } + if (visibleActions.isNotEmpty()) { + view.requireViewById<View>(R.id.actions_container_background).visibility = View.VISIBLE + } - for (child in actionsContainer.children.toList()) { - if (child.tag !in newIds) { - actionsContainer.removeView(child) - } - } + // Remove any buttons not in the new list, then do another pass to add + // any new actions and update any that are already there. + // This assumes that actions can never change order and that each action + // ID is unique. + val newIds = visibleActions.map { it.id } - for ((index, action) in visibleActions.withIndex()) { - val currentView: View? = actionsContainer.getChildAt(index) - if (action.id == currentView?.tag) { - // Same ID, update the display - ActionButtonViewBinder.bind(currentView, action) - } else { - // Different ID. Removals have already happened so this must - // mean that the new action must be inserted here. - val actionButton = - layoutInflater.inflate( - R.layout.shelf_action_chip, - actionsContainer, - false - ) - actionsContainer.addView(actionButton, index) - ActionButtonViewBinder.bind(actionButton, action) - } - } - } - } - } + for (child in actionsContainer.children.toList()) { + if (child.tag !in newIds) { + actionsContainer.removeView(child) + } + } + + for ((index, action) in visibleActions.withIndex()) { + val currentView: View? = actionsContainer.getChildAt(index) + if (action.id == currentView?.tag) { + // Same ID, update the display + buttonViewBinder.bind(currentView, action) + } else { + // Different ID. Removals have already happened so this must + // mean that the new action must be inserted here. + val actionButton = + layoutInflater.inflate(R.layout.shelf_action_chip, actionsContainer, false) + actionsContainer.addView(actionButton, index) + buttonViewBinder.bind(actionButton, action) } } } @@ -150,4 +230,35 @@ object ScreenshotShelfViewBinder { screenshotPreview.layoutParams = params screenshotPreview.requestLayout() } + + private fun setScrollablePreview( + scrollablePreview: ImageView, + bitmap: Bitmap?, + scrollableRect: Rect + ) { + if (bitmap == null) { + return + } + val fixedSize = scrollablePreview.resources.getDimensionPixelSize(R.dimen.overlay_x_scale) + val inPortrait = + scrollablePreview.resources.configuration.orientation == + Configuration.ORIENTATION_PORTRAIT + val scale: Float = fixedSize / ((if (inPortrait) bitmap.width else bitmap.height).toFloat()) + val params = scrollablePreview.layoutParams + + params.width = (scale * scrollableRect.width()).toInt() + params.height = (scale * scrollableRect.height()).toInt() + val matrix = Matrix() + matrix.setScale(scale, scale) + matrix.postTranslate(-scrollableRect.left * scale, -scrollableRect.top * scale) + + scrollablePreview.translationX = + (scale * + if (scrollablePreview.layoutDirection == LayoutDirection.LTR) scrollableRect.left + else scrollableRect.right - (scrollablePreview.parent as View).width) + scrollablePreview.translationY = scale * scrollableRect.top + scrollablePreview.setImageMatrix(matrix) + scrollablePreview.setImageBitmap(bitmap) + scrollablePreview.setVisibility(View.VISIBLE) + } } 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..42ad326c6b45 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,12 @@ 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 tint: Boolean = true, + val customBackground: Drawable? = null, ) diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt index c5fa8db953fa..364ab7624b5e 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ActionButtonViewModel.kt @@ -20,6 +20,7 @@ data class ActionButtonViewModel( val appearance: ActionButtonAppearance, val id: Int, val visible: Boolean, + val showDuringEntrance: Boolean, val onClicked: (() -> Unit)?, ) { companion object { @@ -29,7 +30,14 @@ data class ActionButtonViewModel( fun withNextId( appearance: ActionButtonAppearance, + showDuringEntrance: Boolean, onClicked: (() -> Unit)? - ): ActionButtonViewModel = ActionButtonViewModel(appearance, getId(), true, onClicked) + ): ActionButtonViewModel = + ActionButtonViewModel(appearance, getId(), true, showDuringEntrance, onClicked) + + fun withNextId( + appearance: ActionButtonAppearance, + onClicked: (() -> Unit)? + ): ActionButtonViewModel = withNextId(appearance, showDuringEntrance = true, onClicked) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt index f67ad402a738..3f99bc4597cb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt @@ -17,6 +17,8 @@ package com.android.systemui.screenshot.ui.viewmodel import android.graphics.Bitmap +import android.graphics.Rect +import android.graphics.drawable.Drawable import android.util.Log import android.view.accessibility.AccessibilityManager import kotlinx.coroutines.flow.MutableStateFlow @@ -25,10 +27,21 @@ import kotlinx.coroutines.flow.StateFlow class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager) { private val _preview = MutableStateFlow<Bitmap?>(null) val preview: StateFlow<Bitmap?> = _preview + private val _scrollingScrim = MutableStateFlow<Bitmap?>(null) + val scrollingScrim: StateFlow<Bitmap?> = _scrollingScrim + private val _badge = MutableStateFlow<Drawable?>(null) + val badge: StateFlow<Drawable?> = _badge private val _previewAction = MutableStateFlow<(() -> Unit)?>(null) val previewAction: StateFlow<(() -> Unit)?> = _previewAction private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>()) val actions: StateFlow<List<ActionButtonViewModel>> = _actions + private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED) + val animationState: StateFlow<AnimationState> = _animationState + + private val _isAnimating = MutableStateFlow(false) + val isAnimating: StateFlow<Boolean> = _isAnimating + private val _scrollableRect = MutableStateFlow<Rect?>(null) + val scrollableRect: StateFlow<Rect?> = _scrollableRect val showDismissButton: Boolean get() = accessibilityManager.isEnabled @@ -36,13 +49,26 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager _preview.value = bitmap } + fun setScrollingScrimBitmap(bitmap: Bitmap?) { + _scrollingScrim.value = bitmap + } + + fun setScreenshotBadge(badge: Drawable?) { + _badge.value = badge + } + fun setPreviewAction(onClick: () -> Unit) { _previewAction.value = onClick } - fun addAction(actionAppearance: ActionButtonAppearance, onClicked: (() -> Unit)): Int { + fun addAction( + actionAppearance: ActionButtonAppearance, + showDuringEntrance: Boolean, + onClicked: (() -> Unit) + ): Int { val actionList = _actions.value.toMutableList() - val action = ActionButtonViewModel.withNextId(actionAppearance, onClicked) + val action = + ActionButtonViewModel.withNextId(actionAppearance, showDuringEntrance, onClicked) actionList.add(action) _actions.value = actionList return action.id @@ -57,6 +83,7 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager actionList[index].appearance, actionId, visible, + actionList[index].showDuringEntrance, actionList[index].onClicked ) _actions.value = actionList @@ -74,6 +101,7 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager appearance, actionId, actionList[index].visible, + actionList[index].showDuringEntrance, actionList[index].onClicked ) _actions.value = actionList @@ -92,13 +120,38 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager } } + // TODO: this should be handled entirely within the view binder. + fun setAnimationState(state: AnimationState) { + _animationState.value = state + } + + fun setIsAnimating(isAnimating: Boolean) { + _isAnimating.value = isAnimating + } + + fun setScrollableRect(rect: Rect?) { + _scrollableRect.value = rect + } + fun reset() { _preview.value = null + _scrollingScrim.value = null + _badge.value = null _previewAction.value = null _actions.value = listOf() + _animationState.value = AnimationState.NOT_STARTED + _isAnimating.value = false + _scrollableRect.value = null } companion object { const val TAG = "ScreenshotViewModel" } } + +enum class AnimationState { + NOT_STARTED, + ENTRANCE_STARTED, // The first 200ms of the entrance animation + ENTRANCE_REVEAL, // The rest of the entrance animation + ENTRANCE_COMPLETE, +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java index 288ff09602f4..84156eeb9264 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ToggleSeekBar.java @@ -51,6 +51,16 @@ public class ToggleSeekBar extends SeekBar { return super.onTouchEvent(event); } + @Override + public boolean onHoverEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_HOVER_ENTER) { + setHovered(true); + } else if (event.getAction() == MotionEvent.ACTION_HOVER_EXIT) { + setHovered(false); + } + return true; + } + public void setAccessibilityLabel(String label) { mAccessibilityLabel = label; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 851bfca56359..22aa492dbfe8 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 @@ -47,12 +48,12 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.lifecycle.repeatWhenAttached 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.and +import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not -import com.android.systemui.util.kotlin.BooleanFlowOperators.or import com.android.systemui.util.kotlin.collectFlow import javax.inject.Inject import kotlinx.coroutines.flow.Flow @@ -68,13 +69,12 @@ class GlanceableHubContainerController constructor( private val communalInteractor: CommunalInteractor, private val communalViewModel: CommunalViewModel, - private val dialogFactory: SystemUIDialogFactory, - private val keyguardTransitionInteractor: KeyguardTransitionInteractor, 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. */ @@ -102,12 +102,9 @@ constructor( private var rightEdgeSwipeRegionWidth: Int = 0 /** - * True if we are currently tracking a gesture for opening the hub that started in the edge - * swipe region. + * True if we are currently tracking a touch intercepted by the hub, either because the hub is + * open or being opened. */ - private var isTrackingOpenGesture = false - - /** True if we are currently tracking a touch on the hub while it's open. */ private var isTrackingHubTouch = false /** @@ -126,15 +123,9 @@ constructor( private var anyBouncerShowing = false /** - * True if the shade is fully expanded and the user is not interacting with it anymore, meaning - * the hub should not receive any touch input. - * - * We need to not pause the touch handling lifecycle as soon as the shade opens because if the - * user swipes down, then back up without lifting their finger, the lifecycle will be paused - * then resumed, and resuming force-stops all active touch sessions. This means the shade will - * not receive the end of the gesture and will be stuck open. + * True if the shade is fully expanded, meaning the hub should not receive any touch input. * - * Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting]. + * Tracks [ShadeInteractor.isAnyFullyExpanded]. */ private var shadeShowing = false @@ -148,12 +139,12 @@ constructor( /** Returns a flow that tracks whether communal hub is available. */ fun communalAvailable(): Flow<Boolean> = - or(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen) + anyOf(communalInteractor.isCommunalAvailable, communalInteractor.editModeOpen) /** * Creates the container view containing the glanceable hub UI. * - * @throws RuntimeException if [isEnabled] is false or the view is already initialized + * @throws RuntimeException if the view is already initialized */ fun initView( context: Context, @@ -183,7 +174,7 @@ constructor( viewModel = communalViewModel, colors = communalColors, dataSourceDelegator = dataSourceDelegator, - dialogFactory = dialogFactory, + content = communalContent, ) } } @@ -197,6 +188,7 @@ constructor( /** Override for testing. */ @VisibleForTesting internal fun initView(containerView: View): View { + SceneContainerFlag.assertInLegacyMode() if (communalContainerView != null) { throw RuntimeException("Communal view has already been initialized") } @@ -227,7 +219,7 @@ constructor( // BouncerSwipeTouchHandler has a larger gesture area than we want, set an exclusion area so // the gesture area doesn't overlap with widgets. - // TODO(b/323035776): adjust gesture areaa for portrait mode + // TODO(b/323035776): adjust gesture area for portrait mode containerView.repeatWhenAttached { // Run when the touch handling lifecycle is RESUMED, meaning the hub is visible and not // occluded. @@ -250,7 +242,7 @@ constructor( // transition to the bouncer would be incorrectly intercepted by the hub. collectFlow( containerView, - or( + anyOf( keyguardInteractor.primaryBouncerShowing, keyguardInteractor.alternateBouncerShowing ), @@ -261,7 +253,7 @@ constructor( ) collectFlow( containerView, - communalInteractor.isCommunalShowing, + communalInteractor.isCommunalVisible, { hubShowing = it updateTouchHandlingState() @@ -269,7 +261,7 @@ constructor( ) collectFlow( containerView, - and(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)), + allOf(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)), { shadeShowing = it updateTouchHandlingState() @@ -306,6 +298,7 @@ constructor( /** Removes the container view from its parent. */ fun disposeView() { + SceneContainerFlag.assertInLegacyMode() communalContainerView?.let { (it.parent as ViewGroup).removeView(it) lifecycleRegistry.currentState = Lifecycle.State.CREATED @@ -323,20 +316,11 @@ constructor( * to be fully in control of its own touch handling. */ fun onTouchEvent(ev: MotionEvent): Boolean { + SceneContainerFlag.assertInLegacyMode() return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false } private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean { - // If the hub is fully visible, send all touch events to it, other than top and bottom edge - // swipes. - return if (hubShowing) { - handleHubOpenTouch(view, ev) - } else { - handleHubClosedTouch(view, ev) - } - } - - private fun handleHubOpenTouch(view: View, ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL @@ -344,50 +328,18 @@ constructor( val hubOccluded = anyBouncerShowing || shadeShowing if (isDown && !hubOccluded) { - // Only intercept down events if the hub isn't occluded by the bouncer or - // notification shade. - isTrackingHubTouch = true - } - - if (isTrackingHubTouch) { - // Tracking a touch on the hub UI itself. - if (isUp || isCancel) { - isTrackingHubTouch = false - } - dispatchTouchEvent(view, ev) - // Return true regardless of dispatch result as some touches at the start of a - // gesture - // may return false from dispatchTouchEvent. - return true - } - - return false - } - - private fun handleHubClosedTouch(view: View, ev: MotionEvent): Boolean { - val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN - val isUp = ev.actionMasked == MotionEvent.ACTION_UP - val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL - - val hubOccluded = anyBouncerShowing || shadeShowing - - if (rightEdgeSwipeRegionWidth == 0) { - // If the edge region width has not been read yet for whatever reason, don't bother - // intercepting touches to open the hub. - return false - } - - if (isDown && !hubOccluded) { val x = ev.rawX val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth - if (inOpeningSwipeRegion) { - isTrackingOpenGesture = true + if (inOpeningSwipeRegion || hubShowing) { + // Steal touch events when the hub is open, or if the touch started in the opening + // gesture region. + isTrackingHubTouch = true } } - if (isTrackingOpenGesture) { + if (isTrackingHubTouch) { if (isUp || isCancel) { - isTrackingOpenGesture = false + isTrackingHubTouch = false } dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 4d4d17748ba7..3826b50ab024 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -25,6 +25,7 @@ import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; import static com.android.systemui.Flags.predictiveBackAnimateShade; +import static com.android.systemui.Flags.shadeCollapseActivityLaunchFix; import static com.android.systemui.Flags.smartspaceRelocateToBottom; import static com.android.systemui.classifier.Classifier.BOUNCER_UNLOCK; import static com.android.systemui.classifier.Classifier.GENERIC; @@ -140,6 +141,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver; import com.android.systemui.keyguard.shared.ComposeLockscreen; +import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.binder.KeyguardLongPressViewBinder; @@ -169,6 +171,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.WakefulnessModel; import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.shade.data.repository.FlingInfo; import com.android.systemui.shade.data.repository.ShadeRepository; import com.android.systemui.shade.domain.interactor.ShadeAnimationInteractor; @@ -250,6 +253,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; import javax.inject.Inject; @@ -446,6 +450,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final ShadeHeadsUpTrackerImpl mShadeHeadsUpTracker = new ShadeHeadsUpTrackerImpl(); private final ShadeFoldAnimatorImpl mShadeFoldAnimator = new ShadeFoldAnimatorImpl(); + @VisibleForTesting + Set<Animator> mTestSetOfAnimatorsUsed; + private boolean mShowIconsWhenExpanded; private int mIndicationBottomPadding; private int mAmbientIndicationBottomPadding; @@ -619,6 +626,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private int mDreamingToLockscreenTransitionTranslationY; private int mLockscreenToDreamingTransitionTranslationY; private int mGoneToDreamingTransitionTranslationY; + private boolean mForceFlingAnimationForTest = false; private final SplitShadeStateController mSplitShadeStateController; private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */, mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */); @@ -1128,8 +1136,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump controller.setup(mNotificationContainerParent)); // Dreaming->Lockscreen - collectFlow(mView, mKeyguardTransitionInteractor.transition(DREAMING, LOCKSCREEN), - mDreamingToLockscreenTransition, mMainDispatcher); + collectFlow( + mView, + mKeyguardTransitionInteractor.transition( + Edge.Companion.create(DREAMING, LOCKSCREEN)), + mDreamingToLockscreenTransition, + mMainDispatcher); collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(), setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher); @@ -1139,7 +1151,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Gone -> Dreaming hosted in lockscreen collectFlow(mView, mKeyguardTransitionInteractor - .transition(GONE, DREAMING_LOCKSCREEN_HOSTED), + .transition(Edge.Companion.create(Scenes.Gone, DREAMING_LOCKSCREEN_HOSTED), + Edge.Companion.create(GONE, DREAMING_LOCKSCREEN_HOSTED)), mGoneToDreamingLockscreenHostedTransition, mMainDispatcher); collectFlow(mView, mGoneToDreamingLockscreenHostedTransitionViewModel.getLockscreenAlpha(), setTransitionAlpha(mNotificationStackScrollLayoutController), @@ -1147,16 +1160,17 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // Lockscreen -> Dreaming hosted in lockscreen collectFlow(mView, mKeyguardTransitionInteractor - .transition(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED), + .transition(Edge.Companion.create(LOCKSCREEN, DREAMING_LOCKSCREEN_HOSTED)), mLockscreenToDreamingLockscreenHostedTransition, mMainDispatcher); // Dreaming hosted in lockscreen -> Lockscreen collectFlow(mView, mKeyguardTransitionInteractor - .transition(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN), + .transition(Edge.Companion.create(DREAMING_LOCKSCREEN_HOSTED, LOCKSCREEN)), mDreamingLockscreenHostedToLockscreenTransition, mMainDispatcher); // Occluded->Lockscreen - collectFlow(mView, mKeyguardTransitionInteractor.transition(OCCLUDED, LOCKSCREEN), + collectFlow(mView, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(OCCLUDED, LOCKSCREEN)), mOccludedToLockscreenTransition, mMainDispatcher); if (!MigrateClocksToBlueprint.isEnabled()) { collectFlow(mView, mOccludedToLockscreenTransitionViewModel.getLockscreenAlpha(), @@ -1167,7 +1181,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } // Lockscreen->Dreaming - collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING), + collectFlow(mView, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(LOCKSCREEN, DREAMING)), mLockscreenToDreamingTransition, mMainDispatcher); if (!MigrateClocksToBlueprint.isEnabled()) { collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(), @@ -1179,7 +1194,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); // Gone->Dreaming - collectFlow(mView, mKeyguardTransitionInteractor.transition(GONE, DREAMING), + collectFlow(mView, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(Scenes.Gone, DREAMING), + Edge.Companion.create(GONE, DREAMING)), mGoneToDreamingTransition, mMainDispatcher); if (!MigrateClocksToBlueprint.isEnabled()) { collectFlow(mView, mGoneToDreamingTransitionViewModel.getLockscreenAlpha(), @@ -1190,7 +1207,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher); // Lockscreen->Occluded - collectFlow(mView, mKeyguardTransitionInteractor.transition(LOCKSCREEN, OCCLUDED), + collectFlow(mView, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(LOCKSCREEN, OCCLUDED)), mLockscreenToOccludedTransition, mMainDispatcher); if (!MigrateClocksToBlueprint.isEnabled()) { collectFlow(mView, mLockscreenToOccludedTransitionViewModel.getLockscreenAlpha(), @@ -2217,11 +2235,19 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } } }); + if (!mScrimController.isScreenOn() && !mForceFlingAnimationForTest) { + animator.setDuration(1); + } setAnimator(animator); animator.start(); } @VisibleForTesting + void setForceFlingAnimationForTest(boolean force) { + mForceFlingAnimationForTest = force; + } + + @VisibleForTesting void onFlingEnd(boolean cancelled) { mIsFlinging = false; // No overshoot when the animation ends @@ -4102,7 +4128,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump @Override public boolean canBeCollapsed() { - return !isFullyCollapsed() && !isTracking() && !isClosing(); + return !isFullyCollapsed() && !isTracking() && !isClosing() + // Don't try to collapse if on keyguard, as the expansion fraction is 1 in this + // case. + && !(shadeCollapseActivityLaunchFix() && mExpandedFraction == 1f + && mBarState == KEYGUARD); } public void instantCollapse() { @@ -4123,6 +4153,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void setAnimator(ValueAnimator animator) { + // TODO(b/341163515): Should we clean up the old animator? + registerAnimatorForTest(animator); mHeightAnimator = animator; if (animator == null && mPanelUpdateWhenAnimatorEnds) { mPanelUpdateWhenAnimatorEnds = false; @@ -4167,6 +4199,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) { float startExpansion = mOverExpansion; ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight); + registerAnimatorForTest(animator); animator.addUpdateListener( animation -> { if (overshootAmount > 0.0f @@ -4184,6 +4217,12 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return animator; } + private void registerAnimatorForTest(Animator animator) { + if (mTestSetOfAnimatorsUsed != null && animator != null) { + mTestSetOfAnimatorsUsed.add(animator); + } + } + /** Update the visibility of {@link NotificationPanelView} if necessary. */ private void updateVisibility() { mView.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 4a636d28aa88..3eb43895c7ae 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -412,9 +412,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } if (state.bouncerShowing) { - mLpChanged.inputFeatures |= LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING; + mLpChanged.inputFeatures |= LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; } else { - mLpChanged.inputFeatures &= ~LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING; + mLpChanged.inputFeatures &= ~LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index 44f86da7431e..6efa6334b968 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -49,9 +49,11 @@ import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener; import com.android.systemui.statusbar.DragDownHelper; @@ -136,11 +138,6 @@ public class NotificationShadeWindowViewController implements Dumpable { private final PanelExpansionInteractor mPanelExpansionInteractor; private final ShadeExpansionStateManager mShadeExpansionStateManager; - /** - * If {@code true}, an external touch sent in {@link #handleExternalTouch(MotionEvent)} has been - * intercepted and all future touch events for the gesture should be processed by this view. - */ - private boolean mExternalTouchIntercepted = false; private boolean mIsTrackingBarGesture = false; private boolean mIsOcclusionTransitionRunning = false; private DisableSubpixelTextTransitionListener mDisableSubpixelTextTransitionListener; @@ -224,7 +221,8 @@ public class NotificationShadeWindowViewController implements Dumpable { mDisableSubpixelTextTransitionListener = new DisableSubpixelTextTransitionListener(mView); bouncerViewBinder.bind(mView.findViewById(R.id.keyguard_bouncer_container)); - collectFlow(mView, keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING), + collectFlow(mView, keyguardTransitionInteractor.transition( + Edge.Companion.create(LOCKSCREEN, DREAMING)), mLockscreenToDreamingTransition); collectFlow( mView, @@ -257,28 +255,11 @@ public class NotificationShadeWindowViewController implements Dumpable { } /** - * Handle a touch event while dreaming or on the hub by forwarding the event to the content - * view. - * <p> - * Since important logic for handling touches lives in the dispatch/intercept phases, we - * simulate going through all of these stages before sending onTouchEvent if intercepted. - * + * Handle a touch event while dreaming by forwarding the event to the content view. * @param event The event to forward. */ - public void handleExternalTouch(MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - mExternalTouchIntercepted = false; - } - - if (!mView.dispatchTouchEvent(event)) { - return; - } - if (!mExternalTouchIntercepted) { - mExternalTouchIntercepted = mView.onInterceptTouchEvent(event); - } - if (mExternalTouchIntercepted) { - mView.onTouchEvent(event); - } + public void handleDreamTouch(MotionEvent event) { + mView.dispatchTouchEvent(event); } /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */ @@ -357,7 +338,9 @@ public class NotificationShadeWindowViewController implements Dumpable { mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); - if (mGlanceableHubContainerController.onTouchEvent(ev)) { + if (!SceneContainerFlag.isEnabled() + && mGlanceableHubContainerController.onTouchEvent(ev)) { + // GlanceableHubContainerController is only used pre-flexiglass. return logDownDispatch(ev, "dispatched to glanceable hub container", true); } if (mDreamingWakeupGestureHandler != null @@ -621,6 +604,10 @@ public class NotificationShadeWindowViewController implements Dumpable { * The layout lives in {@link R.id.communal_ui_stub}. */ public void setupCommunalHubLayout() { + if (SceneContainerFlag.isEnabled()) { + // GlanceableHubContainerController is only used pre-flexiglass. + return; + } collectFlow( mView, mGlanceableHubContainerController.communalAvailable(), diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt index 34629934e467..864e39af2da7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerSceneImpl.kt @@ -33,7 +33,7 @@ constructor( get() = shadeInteractor.isQsExpanded.value override val isCustomizing: Boolean - get() = qsSceneAdapter.isCustomizing.value + get() = qsSceneAdapter.isCustomizerShowing.value @Deprecated("specific to legacy touch handling") override fun shouldQuickSettingsIntercept(x: Float, y: Float, yDiff: Float): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index 8c15817898a8..884ccef3a080 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -28,10 +28,10 @@ import com.android.systemui.log.LogBuffer import com.android.systemui.log.dagger.ShadeTouchLog import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.scene.shared.model.TransitionKeys.CollapseShadeInstantly import com.android.systemui.scene.shared.model.TransitionKeys.SlightlyFasterShadeCollapse import com.android.systemui.shade.ShadeController.ShadeVisibilityListener import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.VibratorHelper @@ -99,11 +99,9 @@ constructor( } override fun instantCollapseShade() { - // TODO(b/325602936) add support for instant transition - sceneInteractor.changeScene( + sceneInteractor.snapToScene( getCollapseDestinationScene(), "hide shade", - CollapseShadeInstantly, ) } @@ -142,7 +140,7 @@ constructor( private fun animateCollapseShadeInternal() { sceneInteractor.changeScene( - getCollapseDestinationScene(), + getCollapseDestinationScene(), // TODO(b/336581871): add sceneState? "ShadeController.animateCollapseShade", SlightlyFasterShadeCollapse, ) @@ -194,11 +192,19 @@ constructor( } override fun expandToNotifications() { - sceneInteractor.changeScene(Scenes.Shade, "ShadeController.animateExpandShade") + val shadeMode = shadeInteractor.shadeMode.value + sceneInteractor.changeScene( + if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade, + "ShadeController.animateExpandShade" + ) } override fun expandToQs() { - sceneInteractor.changeScene(Scenes.QuickSettings, "ShadeController.animateExpandQs") + val shadeMode = shadeInteractor.shadeMode.value + sceneInteractor.changeScene( + if (shadeMode is ShadeMode.Dual) Scenes.QuickSettingsShade else Scenes.QuickSettings, + "ShadeController.animateExpandQs" + ) } override fun setVisibilityListener(listener: ShadeVisibilityListener) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 9dc19b147e4a..daea977c9d09 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -181,11 +181,8 @@ interface ShadeViewStateProvider { /** * Returns true if heads up should be visible. - * - * TODO(b/138786270): If HeadsUpAppearanceController was injectable, we could inject it into - * [KeyguardStatusBarViewController] and remove this method. */ - @Deprecated("deprecated in Flexiglass.") fun shouldHeadsUpBeVisible(): Boolean + @Deprecated("deprecated by SceneContainerFlag.isEnabled.") fun shouldHeadsUpBeVisible(): Boolean /** Return the fraction of the shade that's expanded, when in lockscreen. */ @Deprecated("deprecated by SceneContainerFlag.isEnabled") val lockscreenShadeDragProgress: Float diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index b934d63c17c5..7e1a3109fcbb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow @@ -219,7 +220,7 @@ class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { @Deprecated("Use ShadeInteractor instead") override val legacyQsFullscreen: StateFlow<Boolean> = _legacyQsFullscreen.asStateFlow() - val _shadeMode = MutableStateFlow<ShadeMode>(ShadeMode.Single) + val _shadeMode = MutableStateFlow(if (DualShade.isEnabled) ShadeMode.Dual else ShadeMode.Single) override val shadeMode: StateFlow<ShadeMode> = _shadeMode.asStateFlow() override fun setShadeMode(shadeMode: ShadeMode) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt index 58bcd2e0d7eb..8006e9421f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImpl.kt @@ -55,7 +55,7 @@ constructor( when (state) { is ObservableTransitionState.Idle -> flowOf( - if (state.scene != Scenes.Gone) { + if (state.currentScene != Scenes.Gone) { // When resting on a non-Gone scene, the panel is fully expanded. 1f } else { 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 3a8ba7a0696b..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 @@ -19,6 +19,7 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -34,7 +35,7 @@ constructor( override fun animateCollapseQs(fullyCollapse: Boolean) { if (shadeInteractor.isQsExpanded.value) { val key = - if (fullyCollapse) { + if (fullyCollapse || shadeInteractor.shadeMode.value is ShadeMode.Dual) { if (deviceEntryInteractor.isDeviceEntered.value) { Scenes.Gone } else { @@ -43,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/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index 0de3c10329e3..18407cc8e76f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -38,6 +38,9 @@ interface ShadeInteractor : BaseShadeInteractor { /** Whether the Shade is fully expanded. */ val isShadeFullyExpanded: Flow<Boolean> + /** Whether the Shade is fully collapsed. */ + val isShadeFullyCollapsed: Flow<Boolean> + /** * Whether the user is expanding or collapsing either the shade or quick settings with user * input (i.e. dragging a pointer). This will be true even if the user's input gesture had ended diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt index 883ef97a6aad..bb4baa34dd86 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorEmptyImpl.kt @@ -38,6 +38,7 @@ class ShadeInteractorEmptyImpl @Inject constructor() : ShadeInteractor { override val anyExpansion: StateFlow<Float> = inactiveFlowFloat override val isAnyFullyExpanded: StateFlow<Boolean> = inactiveFlowBoolean override val isShadeFullyExpanded: Flow<Boolean> = inactiveFlowBoolean + override val isShadeFullyCollapsed: Flow<Boolean> = inactiveFlowBoolean override val isAnyExpanded: StateFlow<Boolean> = inactiveFlowBoolean override val isUserInteractingWithShade: Flow<Boolean> = inactiveFlowBoolean override val isUserInteractingWithQs: Flow<Boolean> = inactiveFlowBoolean diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt index 0b45c0834245..06a8d1874e5d 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -74,6 +74,9 @@ constructor( override val isShadeFullyExpanded: Flow<Boolean> = baseShadeInteractor.shadeExpansion.map { it >= 1f }.distinctUntilChanged() + override val isShadeFullyCollapsed: Flow<Boolean> = + baseShadeInteractor.shadeExpansion.map { it <= 0f }.distinctUntilChanged() + override val isUserInteracting: StateFlow<Boolean> = combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs } .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt index 7f35f17954c4..53c10a370868 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt @@ -90,7 +90,7 @@ constructor( sceneInteractor.transitionState .map { state -> when (state) { - is ObservableTransitionState.Idle -> state.scene == Scenes.QuickSettings + is ObservableTransitionState.Idle -> state.currentScene == Scenes.QuickSettings is ObservableTransitionState.Transition -> false } } @@ -122,7 +122,7 @@ constructor( .flatMapLatest { state -> when (state) { is ObservableTransitionState.Idle -> - if (state.scene == sceneKey) { + if (state.currentScene == sceneKey) { flowOf(1f) } else { flowOf(0f) diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt index 6a8b9eec140c..9885fe436e6c 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay @@ -95,8 +96,9 @@ constructor( } private fun changeToShadeScene() { + val shadeMode = shadeInteractor.shadeMode.value sceneInteractor.changeScene( - Scenes.Shade, + if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade, "ShadeLockscreenInteractorImpl.expandToNotifications", ) } diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt index f3802da9bc9b..3f4bcba288b7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt @@ -29,6 +29,7 @@ import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.shade.TouchLogger.Companion.logTouchesTo import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor +import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.transition.ScrimShadeTransitionController import com.android.systemui.statusbar.policy.SplitShadeStateController @@ -67,19 +68,24 @@ constructor( private fun hydrateShadeExpansionStateManager() { if (SceneContainerFlag.isEnabled) { combine( - panelExpansionInteractorProvider.get().legacyPanelExpansion, - sceneInteractorProvider.get().isTransitionUserInputOngoing, - ) { panelExpansion, tracking -> - shadeExpansionStateManager.onPanelExpansionChanged( - fraction = panelExpansion, - expanded = panelExpansion > 0f, - tracking = tracking, - ) - }.launchIn(applicationScope) + panelExpansionInteractorProvider.get().legacyPanelExpansion, + sceneInteractorProvider.get().isTransitionUserInputOngoing, + ) { panelExpansion, tracking -> + shadeExpansionStateManager.onPanelExpansionChanged( + fraction = panelExpansion, + expanded = panelExpansion > 0f, + tracking = tracking, + ) + } + .launchIn(applicationScope) } } private fun hydrateShadeMode() { + if (DualShade.isEnabled) { + shadeRepository.setShadeMode(ShadeMode.Dual) + return + } applicationScope.launch { configurationRepository.onAnyConfigurationChange // Force initial collection. @@ -90,11 +96,7 @@ constructor( } .collect { isSplitShade -> shadeRepository.setShadeMode( - if (isSplitShade) { - ShadeMode.Split - } else { - ShadeMode.Single - } + if (isSplitShade) ShadeMode.Split else ShadeMode.Single ) } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt index 3451eaf54063..8214a24a9a38 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt @@ -38,4 +38,8 @@ sealed interface ShadeMode { * a space on a small screen or folded device. */ data object Dual : ShadeMode + + companion object { + @JvmStatic fun dual(): Dual = Dual + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt new file mode 100644 index 000000000000..b8dd62897587 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade.ui.viewmodel + +import com.android.compose.animation.scene.SceneKey +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.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * Models UI state and handles user input for the overlay shade UI, which shows a shade as an + * overlay on top of another scene UI. + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class OverlayShadeViewModel +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val sceneInteractor: SceneInteractor, + deviceEntryInteractor: DeviceEntryInteractor, +) { + /** The scene to show in the background when the overlay shade is open. */ + val backgroundScene: StateFlow<SceneKey> = + deviceEntryInteractor.isDeviceEntered + .map(::backgroundScene) + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = backgroundScene(deviceEntryInteractor.isDeviceEntered.value) + ) + + /** Notifies that the user has clicked the semi-transparent background scrim. */ + fun onScrimClicked() { + sceneInteractor.changeScene( + toScene = backgroundScene.value, + loggingReason = "Shade scrim clicked", + ) + } + + private fun backgroundScene(isDeviceEntered: Boolean): SceneKey { + return if (isDeviceEntered) Scenes.Gone else Scenes.Lockscreen + } +} 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 5b76acb1df58..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, @@ -72,13 +73,13 @@ constructor( deviceEntryInteractor.isUnlocked, deviceEntryInteractor.canSwipeToEnter, shadeInteractor.shadeMode, - qsSceneAdapter.isCustomizing - ) { isUnlocked, canSwipeToDismiss, shadeMode, isCustomizing -> + qsSceneAdapter.isCustomizerShowing + ) { isUnlocked, canSwipeToDismiss, shadeMode, isCustomizerShowing -> destinationScenes( isUnlocked = isUnlocked, canSwipeToDismiss = canSwipeToDismiss, shadeMode = shadeMode, - isCustomizing = isCustomizing + isCustomizing = isCustomizerShowing ) } .stateIn( @@ -89,7 +90,7 @@ constructor( isUnlocked = deviceEntryInteractor.isUnlocked.value, canSwipeToDismiss = deviceEntryInteractor.canSwipeToEnter.value, shadeMode = shadeInteractor.shadeMode.value, - isCustomizing = qsSceneAdapter.isCustomizing.value, + isCustomizing = qsSceneAdapter.isCustomizerShowing.value, ), ) @@ -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/slice/SliceViewManagerExt.kt b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt index 384acc493c4a..dd7942503211 100644 --- a/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/slice/SliceViewManagerExt.kt @@ -28,6 +28,9 @@ import kotlinx.coroutines.launch * Returns updating [Slice] for a [sliceUri]. It's null when there is no slice available for the * provided Uri. This can change overtime because of external changes (like device being * connected/disconnected). + * + * The flow should be [kotlinx.coroutines.flow.flowOn] the main thread because [SliceViewManager] + * isn't thread-safe. An exception will be thrown otherwise. */ fun SliceViewManager.sliceForUri(sliceUri: Uri): Flow<Slice?> = ConflatedCallbackFlow.conflatedCallbackFlow { 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/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 78e108d444d0..0d8030f02948 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -97,7 +97,7 @@ import java.util.Map; public final class KeyboardShortcutListSearch { private static final String TAG = KeyboardShortcutListSearch.class.getSimpleName(); private static final Object sLock = new Object(); - @VisibleForTesting static KeyboardShortcutListSearch sInstance; + @VisibleForTesting public static KeyboardShortcutListSearch sInstance; private static int SHORTCUT_SYSTEM_INDEX = 0; private static int SHORTCUT_INPUT_INDEX = 1; @@ -136,7 +136,7 @@ public final class KeyboardShortcutListSearch { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); - @VisibleForTesting Context mContext; + @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; @VisibleForTesting BottomSheetDialog mKeyboardShortcutsBottomSheetDialog; @@ -414,7 +414,7 @@ public final class KeyboardShortcutListSearch { private boolean mImeShortcutsReceived; @VisibleForTesting - void showKeyboardShortcuts(int deviceId) { + public void showKeyboardShortcuts(int deviceId) { retrieveKeyCharacterMap(deviceId); mAppShortcutsReceived = false; mImeShortcutsReceived = false; @@ -502,7 +502,8 @@ public final class KeyboardShortcutListSearch { return keyboardShortcutMultiMappingGroups; } - private void dismissKeyboardShortcuts() { + @VisibleForTesting + public void dismissKeyboardShortcuts() { if (mKeyboardShortcutsBottomSheetDialog != null) { mKeyboardShortcutsBottomSheetDialog.dismiss(); mKeyboardShortcutsBottomSheetDialog = null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index 90567d8ec934..21f608e13f5c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -79,7 +79,7 @@ import java.util.List; public final class KeyboardShortcuts { private static final String TAG = KeyboardShortcuts.class.getSimpleName(); private static final Object sLock = new Object(); - @VisibleForTesting static KeyboardShortcuts sInstance; + @VisibleForTesting public static KeyboardShortcuts sInstance; private WindowManager mWindowManager; private final SparseArray<String> mSpecialCharacterNames = new SparseArray<>(); @@ -93,7 +93,7 @@ public final class KeyboardShortcuts { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); - @VisibleForTesting Context mContext; + @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { @@ -373,7 +373,7 @@ public final class KeyboardShortcuts { } @VisibleForTesting - void showKeyboardShortcuts(int deviceId) { + public void showKeyboardShortcuts(int deviceId) { retrieveKeyCharacterMap(deviceId); mReceivedAppShortcutGroups = null; mReceivedImeShortcutGroups = null; @@ -407,7 +407,8 @@ public final class KeyboardShortcuts { showKeyboardShortcutsDialog(shortcutGroups); } - private void dismissKeyboardShortcuts() { + @VisibleForTesting + public void dismissKeyboardShortcuts() { if (mKeyboardShortcutsDialog != null) { mKeyboardShortcutsDialog.dismiss(); mKeyboardShortcutsDialog = null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java index 1cfb400280fe..815f1fcfdec6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java @@ -15,6 +15,8 @@ */ package com.android.systemui.statusbar; +import static com.android.systemui.Flags.keyboardShortcutHelperRewrite; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -25,21 +27,22 @@ import com.android.systemui.shared.recents.utilities.Utilities; import javax.inject.Inject; -/** - * Receiver for the Keyboard Shortcuts Helper. - */ +/** Receiver for the Keyboard Shortcuts Helper. */ public class KeyboardShortcutsReceiver extends BroadcastReceiver { - private boolean mIsShortcutListSearchEnabled; + private final FeatureFlags mFeatureFlags; @Inject public KeyboardShortcutsReceiver(FeatureFlags featureFlags) { - mIsShortcutListSearchEnabled = featureFlags.isEnabled(Flags.SHORTCUT_LIST_SEARCH_LAYOUT); + mFeatureFlags = featureFlags; } @Override public void onReceive(Context context, Intent intent) { - if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(context)) { + if (keyboardShortcutHelperRewrite()) { + return; + } + if (isTabletLayoutFlagEnabled() && Utilities.isLargeScreen(context)) { if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) { KeyboardShortcutListSearch.show(context, -1 /* deviceId unknown */); } else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) { @@ -53,4 +56,8 @@ public class KeyboardShortcutsReceiver extends BroadcastReceiver { } } } + + private boolean isTabletLayoutFlagEnabled() { + return mFeatureFlags.isEnabled(Flags.SHORTCUT_LIST_SEARCH_LAYOUT); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 7983db137e76..3d4b421fcc50 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; @@ -631,6 +633,7 @@ public class KeyguardIndicationController { INDICATION_TYPE_BIOMETRIC_MESSAGE, new KeyguardIndication.Builder() .setMessage(mBiometricMessage) + .setForceAccessibilityLiveRegionAssertive() .setMinVisibilityMillis(IMPORTANT_MSG_MIN_DURATION) .setTextColor(mInitialTextColorState) .build(), @@ -1244,7 +1247,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 +1519,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/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 222b070d151d..14e14f4bd47f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -14,7 +14,6 @@ import androidx.annotation.FloatRange import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.ExpandHelper -import com.android.systemui.Flags.nsslFalsingFix import com.android.systemui.Gefingerpoken import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy import com.android.systemui.classifier.Classifier @@ -884,9 +883,7 @@ class DragDownHelper( isDraggingDown = false isTrackpadReverseScroll = false shadeRepository.setLegacyLockscreenShadeTracking(false) - if (nsslFalsingFix() || MigrateClocksToBlueprint.isEnabled) { - return true - } + return true } else { stopDragging() return false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index 7c1101bf7680..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)); }); @@ -382,16 +388,15 @@ public class NotificationMediaManager implements Dumpable { private void clearCurrentMediaNotificationSession() { mMediaMetadata = null; - mBackgroundExecutor.execute(() -> { - if (mMediaController != null) { - if (DEBUG_MEDIA) { - Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " - + mMediaController.getPackageName()); - } - mMediaController.unregisterCallback(mMediaListener); - mMediaController = null; + if (mMediaController != null) { + if (DEBUG_MEDIA) { + Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: " + + mMediaController.getPackageName()); } - }); + // TODO(b/336612071): move to background thread + mMediaController.unregisterCallback(mMediaListener); + } + mMediaController = null; } public interface MediaListener { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 4f8c3caa122c..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); @@ -677,7 +690,9 @@ public class StatusBarStateControllerImpl implements Scenes.Bouncer, StatusBarState.KEYGUARD, Scenes.Communal, StatusBarState.KEYGUARD, Scenes.Shade, StatusBarState.SHADE_LOCKED, + Scenes.NotificationsShade, StatusBarState.SHADE_LOCKED, Scenes.QuickSettings, StatusBarState.SHADE_LOCKED, + Scenes.QuickSettingsShade, StatusBarState.SHADE_LOCKED, Scenes.Gone, StatusBarState.SHADE ); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt index ce88a5f5e55f..cae86a652245 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainer.kt @@ -14,18 +14,18 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone.ongoingcall +package com.android.systemui.statusbar.chips.ui.view import android.content.Context import android.util.AttributeSet import com.android.systemui.animation.view.LaunchableLinearLayout /** - * A container view for the ongoing call chip background. Needed so that we can limit the height of - * the background when the font size is very large (200%), in which case the background would go + * A container view for the ongoing activity chip background. Needed so that we can limit the height + * of the background when the font size is very large (200%), in which case the background would go * past the bounds of the status bar. */ -class OngoingCallBackgroundContainer(context: Context, attrs: AttributeSet) : +class ChipBackgroundContainer(context: Context, attrs: AttributeSet) : LaunchableLinearLayout(context, attrs) { /** Sets where this view should fetch its max height from. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt index bb7ba4c4174f..ff3061e850d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometer.kt @@ -14,36 +14,34 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone.ongoingcall +package com.android.systemui.statusbar.chips.ui.view import android.content.Context import android.util.AttributeSet - import android.widget.Chronometer import androidx.annotation.UiThread /** - * A [Chronometer] specifically for the ongoing call chip in the status bar. + * A [Chronometer] specifically for chips in the status bar that show ongoing duration of an + * activity. * * This class handles: - * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would - * change slightly each second because the width of each number is slightly different. - * - * Instead, we save the largest number width seen so far and ensure that the chip is at least - * that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 - * to 1:00:00), but never smaller. + * 1) Setting the text width. If we used a basic WRAP_CONTENT for width, the chip width would change + * slightly each second because the width of each number is slightly different. * - * 2) Hiding the text if the time gets too long for the space available. Once the text has been - * hidden, it remains hidden for the duration of the call. + * Instead, we save the largest number width seen so far and ensure that the chip is at least + * that wide. This means the chip may get larger over time (e.g. in the transition from 59:59 to + * 1:00:00), but never smaller. + * 2) Hiding the text if the time gets too long for the space available. Once the text has been + * hidden, it remains hidden for the duration of the activity. * * Note that if the text was too big in portrait mode, resulting in the text being hidden, then the * text will also be hidden in landscape (even if there is enough space for it in landscape). */ -class OngoingCallChronometer @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyle: Int = 0 -) : Chronometer(context, attrs, defStyle) { +class ChipChronometer +@JvmOverloads +constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : + Chronometer(context, attrs, defStyle) { // Minimum width that the text view can be. Corresponds with the largest number width seen so // far. @@ -53,8 +51,8 @@ class OngoingCallChronometer @JvmOverloads constructor( private var shouldHideText: Boolean = false override fun setBase(base: Long) { - // These variables may have changed during the previous call, so re-set them before the new - // call starts. + // These variables may have changed during the previous activity, so re-set them before the + // new activity starts. minimumTextWidth = 0 shouldHideText = false visibility = VISIBLE @@ -75,9 +73,7 @@ class OngoingCallChronometer @JvmOverloads constructor( } // Evaluate how wide the text *wants* to be if it had unlimited space. - super.onMeasure( - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - heightMeasureSpec) + super.onMeasure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec) val desiredTextWidth = measuredWidth // Evaluate how wide the text *can* be based on the enforced constraints 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/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 446a0d746227..455c96441927 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -310,11 +310,9 @@ constructor( fun isWeatherEnabled(): Boolean { execution.assertIsMainThread() - val defaultValue = context.getResources().getBoolean( - com.android.internal.R.bool.config_lockscreenWeatherEnabledByDefault) val showWeather = secureSettings.getIntForUser( LOCK_SCREEN_WEATHER_ENABLED, - if (defaultValue) 1 else 0, + 1, userTracker.userId) == 1 return showWeather } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java index 0c341cc92f83..ec3c7d0d6de4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java @@ -27,6 +27,9 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow * (e.g. clicking on a notification, tapping on the settings icon in the notification guts) */ public interface NotificationActivityStarter { + /** Called when the user clicks on the notification bubble icon. */ + void onNotificationBubbleIconClicked(NotificationEntry entry); + /** Called when the user clicks on the surface of a notification. */ void onNotificationClicked(NotificationEntry entry, ExpandableNotificationRow row); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java index d10fac6ea3fc..6487d55197e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java @@ -117,11 +117,14 @@ public final class NotificationClicker implements View.OnClickListener { Notification notification = sbn.getNotification(); if (notification.contentIntent != null || notification.fullScreenIntent != null || row.getEntry().isBubble()) { + row.setBubbleClickListener(v -> + mNotificationActivityStarter.onNotificationBubbleIconClicked(row.getEntry())); row.setOnClickListener(this); row.setOnDragSuccessListener(mOnDragSuccessListener); } else { row.setOnClickListener(null); row.setOnDragSuccessListener(null); + row.setBubbleClickListener(null); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt index bd9383de3bab..2f293e072c84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt @@ -22,11 +22,13 @@ import android.provider.DeviceConfig import com.android.internal.annotations.VisibleForTesting import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVICE import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP import com.android.systemui.statusbar.notification.stack.BUCKET_MEDIA_CONTROLS import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE +import com.android.systemui.statusbar.notification.stack.BUCKET_PRIORITY_PEOPLE import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.Utils @@ -53,6 +55,18 @@ class NotificationSectionsFeatureManager @Inject constructor( } fun getNotificationBuckets(): IntArray { + if (PriorityPeopleSection.isEnabled) { + // We don't need this list to be adaptive, it can be the superset of all features. + return intArrayOf( + BUCKET_MEDIA_CONTROLS, + BUCKET_HEADS_UP, + BUCKET_FOREGROUND_SERVICE, + BUCKET_PRIORITY_PEOPLE, + BUCKET_PEOPLE, + BUCKET_ALERTING, + BUCKET_SILENT, + ) + } return when { isFilteringEnabled() && isMediaControlsEnabled() -> intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_MEDIA_CONTROLS, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index 3d0fd89dce88..af2c1979ff77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -31,8 +31,10 @@ import com.android.systemui.statusbar.notification.dagger.PeopleHeader import com.android.systemui.statusbar.notification.icon.ConversationIconManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType +import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE +import com.android.systemui.statusbar.notification.stack.BUCKET_PRIORITY_PEOPLE import javax.inject.Inject /** @@ -81,6 +83,13 @@ class ConversationCoordinator @Inject constructor( } } + val priorityPeopleSectioner = + object : NotifSectioner("Priority People", BUCKET_PRIORITY_PEOPLE) { + override fun isInSection(entry: ListEntry): Boolean { + return getPeopleType(entry) == TYPE_IMPORTANT_PERSON + } + } + // TODO(b/330193582): Rename to just "People" val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) { override fun isInSection(entry: ListEntry): Boolean { 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..42bf4e7d0787 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 @@ -33,14 +31,20 @@ import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.expansionChanges +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener 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.notification.stack.BUCKET_FOREGROUND_SERVICE import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.headsUpEvents import com.android.systemui.util.asIndenting @@ -107,6 +111,10 @@ constructor( } private fun attachUnseenFilter(pipeline: NotifPipeline) { + if (NotificationMinimalismPrototype.V2.isEnabled) { + pipeline.addPromoter(unseenNotifPromoter) + pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotif) + } pipeline.addFinalizeFilter(unseenNotifFilter) pipeline.addCollectionListener(collectionListener) scope.launch { trackUnseenFilterSettingChanges() } @@ -264,7 +272,10 @@ constructor( } private fun unseenFeatureEnabled(): Flow<Boolean> { - if (notificationMinimalismPrototype()) { + if ( + NotificationMinimalismPrototype.V1.isEnabled || + NotificationMinimalismPrototype.V2.isEnabled + ) { return flowOf(true) } return secureSettings @@ -335,6 +346,57 @@ constructor( } } + private fun pickOutTopUnseenNotif(list: List<ListEntry>) { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return + // Only ever elevate a top unseen notification on keyguard, not even locked shade + if (statusBarStateController.state != StatusBarState.KEYGUARD) { + seenNotificationsInteractor.setTopUnseenNotification(null) + return + } + // On keyguard pick the top-ranked unseen or ongoing notification to elevate + seenNotificationsInteractor.setTopUnseenNotification( + list + .asSequence() + .flatMap { + when (it) { + is NotificationEntry -> listOfNotNull(it) + is GroupEntry -> it.children + else -> error("unhandled type of $it") + } + } + .filter { shouldIgnoreUnseenCheck(it) || it in unseenNotifications } + .minByOrNull { it.ranking.rank } + ) + } + + @VisibleForTesting + internal val unseenNotifPromoter = + object : NotifPromoter("$TAG-unseen") { + override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean = + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false + else + seenNotificationsInteractor.isTopUnseenNotification(child) && + NotificationMinimalismPrototype.V2.ungroupTopUnseen + } + + val unseenNotifSectioner = + object : NotifSectioner("Unseen", BUCKET_FOREGROUND_SERVICE) { + override fun isInSection(entry: ListEntry): Boolean { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return false + if ( + seenNotificationsInteractor.isTopUnseenNotification(entry.representativeEntry) + ) { + return true + } + if (entry !is GroupEntry) { + return false + } + return entry.children.any { + seenNotificationsInteractor.isTopUnseenNotification(it) + } + } + } + @VisibleForTesting internal val unseenNotifFilter = object : NotifFilter("$TAG-unseen") { @@ -342,18 +404,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 +414,12 @@ constructor( * allow seen notifications to appear in the locked shade. */ private fun isOnKeyguard(): Boolean = - if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) { + if (NotificationMinimalismPrototype.V2.isEnabled) { + false // disable this feature under this prototype + } else 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/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt index 36c12a719570..4506385a2fb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt @@ -24,47 +24,51 @@ import com.android.systemui.statusbar.notification.collection.SortBySectionTimeF import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor +import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection import javax.inject.Inject /** - * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the - * Coordinators can register their respective callbacks. + * Handles the attachment of [Coordinator]s to the [NotifPipeline] so that the Coordinators can + * register their respective callbacks. */ interface NotifCoordinators : Coordinator, PipelineDumpable @CoordinatorScope -class NotifCoordinatorsImpl @Inject constructor( - sectionStyleProvider: SectionStyleProvider, - featureFlags: FeatureFlags, - dataStoreCoordinator: DataStoreCoordinator, - hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, - hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, - keyguardCoordinator: KeyguardCoordinator, - rankingCoordinator: RankingCoordinator, - colorizedFgsCoordinator: ColorizedFgsCoordinator, - deviceProvisionedCoordinator: DeviceProvisionedCoordinator, - bubbleCoordinator: BubbleCoordinator, - headsUpCoordinator: HeadsUpCoordinator, - gutsCoordinator: GutsCoordinator, - conversationCoordinator: ConversationCoordinator, - debugModeCoordinator: DebugModeCoordinator, - groupCountCoordinator: GroupCountCoordinator, - groupWhenCoordinator: GroupWhenCoordinator, - mediaCoordinator: MediaCoordinator, - preparationCoordinator: PreparationCoordinator, - remoteInputCoordinator: RemoteInputCoordinator, - rowAlertTimeCoordinator: RowAlertTimeCoordinator, - rowAppearanceCoordinator: RowAppearanceCoordinator, - stackCoordinator: StackCoordinator, - shadeEventCoordinator: ShadeEventCoordinator, - smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, - viewConfigCoordinator: ViewConfigCoordinator, - visualStabilityCoordinator: VisualStabilityCoordinator, - sensitiveContentCoordinator: SensitiveContentCoordinator, - dismissibilityCoordinator: DismissibilityCoordinator, - dreamCoordinator: DreamCoordinator, - statsLoggerCoordinator: NotificationStatsLoggerCoordinator, +class NotifCoordinatorsImpl +@Inject +constructor( + sectionStyleProvider: SectionStyleProvider, + featureFlags: FeatureFlags, + dataStoreCoordinator: DataStoreCoordinator, + hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator, + hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator, + keyguardCoordinator: KeyguardCoordinator, + rankingCoordinator: RankingCoordinator, + colorizedFgsCoordinator: ColorizedFgsCoordinator, + deviceProvisionedCoordinator: DeviceProvisionedCoordinator, + bubbleCoordinator: BubbleCoordinator, + headsUpCoordinator: HeadsUpCoordinator, + gutsCoordinator: GutsCoordinator, + conversationCoordinator: ConversationCoordinator, + debugModeCoordinator: DebugModeCoordinator, + groupCountCoordinator: GroupCountCoordinator, + groupWhenCoordinator: GroupWhenCoordinator, + mediaCoordinator: MediaCoordinator, + preparationCoordinator: PreparationCoordinator, + remoteInputCoordinator: RemoteInputCoordinator, + rowAlertTimeCoordinator: RowAlertTimeCoordinator, + rowAppearanceCoordinator: RowAppearanceCoordinator, + stackCoordinator: StackCoordinator, + shadeEventCoordinator: ShadeEventCoordinator, + smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator, + viewConfigCoordinator: ViewConfigCoordinator, + visualStabilityCoordinator: VisualStabilityCoordinator, + sensitiveContentCoordinator: SensitiveContentCoordinator, + dismissibilityCoordinator: DismissibilityCoordinator, + dreamCoordinator: DreamCoordinator, + statsLoggerCoordinator: NotificationStatsLoggerCoordinator, ) : NotifCoordinators { private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList() @@ -114,6 +118,12 @@ class NotifCoordinatorsImpl @Inject constructor( // Manually add Ordered Sections mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService + if (NotificationMinimalismPrototype.V2.isEnabled) { + mOrderedSections.add(keyguardCoordinator.unseenNotifSectioner) // Unseen (FGS) + } + if (PriorityPeopleSection.isEnabled) { + mOrderedSections.add(conversationCoordinator.priorityPeopleSectioner) // Priority People + } mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting if (!SortBySectionTimeFlag.isEnabled) { mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent @@ -124,22 +134,26 @@ class NotifCoordinatorsImpl @Inject constructor( sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner)) if (SortBySectionTimeFlag.isEnabled) { - sectionStyleProvider.setSilentSections(listOf( + sectionStyleProvider.setSilentSections( + listOf( rankingCoordinator.silentSectioner, rankingCoordinator.minimizedSectioner, - )) + ) + ) } else { - sectionStyleProvider.setSilentSections(listOf( + sectionStyleProvider.setSilentSections( + listOf( conversationCoordinator.peopleSilentSectioner, rankingCoordinator.silentSectioner, rankingCoordinator.minimizedSectioner, - )) + ) + ) } } /** - * Sends the pipeline to each coordinator when the pipeline is ready to accept - * [Pluggable]s, [NotifCollectionListener]s and [NotifLifetimeExtender]s. + * Sends the pipeline to each coordinator when the pipeline is ready to accept [Pluggable]s, + * [NotifCollectionListener]s and [NotifLifetimeExtender]s. */ override fun attach(pipeline: NotifPipeline) { for (c in mCoreCoordinators) { @@ -155,10 +169,11 @@ class NotifCoordinatorsImpl @Inject constructor( * As part of the NotifPipeline dumpable, dumps the list of coordinators; sections are omitted * as they are dumped in the RenderStageManager instead. */ - override fun dumpPipeline(d: PipelineDumper) = with(d) { - dump("core coordinators", mCoreCoordinators) - dump("normal coordinators", mCoordinators) - } + override fun dumpPipeline(d: PipelineDumper) = + with(d) { + dump("core coordinators", mCoreCoordinators) + dump("normal coordinators", mCoordinators) + } companion object { private const val TAG = "NotifCoordinators" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java index 350e88ea9df5..caa6c17ac3d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java @@ -38,6 +38,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager; import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider; +import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor; +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.util.Compile; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -63,6 +65,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE); private final DelayableExecutor mDelayableExecutor; private final HeadsUpManager mHeadsUpManager; + private final SeenNotificationsInteractor mSeenNotificationsInteractor; private final ShadeAnimationInteractor mShadeAnimationInteractor; private final StatusBarStateController mStatusBarStateController; private final JavaAdapter mJavaAdapter; @@ -101,6 +104,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { HeadsUpManager headsUpManager, ShadeAnimationInteractor shadeAnimationInteractor, JavaAdapter javaAdapter, + SeenNotificationsInteractor seenNotificationsInteractor, StatusBarStateController statusBarStateController, VisibilityLocationProvider visibilityLocationProvider, VisualStabilityProvider visualStabilityProvider, @@ -109,6 +113,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { mHeadsUpManager = headsUpManager; mShadeAnimationInteractor = shadeAnimationInteractor; mJavaAdapter = javaAdapter; + mSeenNotificationsInteractor = seenNotificationsInteractor; mVisibilityLocationProvider = visibilityLocationProvider; mVisualStabilityProvider = visualStabilityProvider; mWakefulnessLifecycle = wakefulnessLifecycle; @@ -142,8 +147,15 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable { private final NotifStabilityManager mNotifStabilityManager = new NotifStabilityManager("VisualStabilityCoordinator") { private boolean canMoveForHeadsUp(NotificationEntry entry) { - return entry != null && mHeadsUpManager.isHeadsUpEntry(entry.getKey()) - && !mVisibilityLocationProvider.isInVisibleLocation(entry); + if (entry == null) { + return false; + } + boolean isTopUnseen = NotificationMinimalismPrototype.V2.isEnabled() + && mSeenNotificationsInteractor.isTopUnseenNotification(entry); + if (isTopUnseen || mHeadsUpManager.isHeadsUpEntry(entry.getKey())) { + return !mVisibilityLocationProvider.isInVisibleLocation(entry); + } + return false; } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt index 5c844bcf749c..e2c9e02672d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/ActiveNotificationListRepository.kt @@ -41,6 +41,9 @@ class ActiveNotificationListRepository @Inject constructor() { /** Stats about the list of notifications attached to the shade */ val notifStats = MutableStateFlow(NotifStats.empty) + + /** The key of the top unseen notification */ + val topUnseenNotificationKey = MutableStateFlow<String?>(null) } /** Represents the notification list, comprised of groups and individual notifications. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt index 98b52edcf9cc..4a6553f724c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/HeadsUpNotificationInteractor.kt @@ -18,6 +18,10 @@ package com.android.systemui.statusbar.notification.domain.interactor +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.data.repository.HeadsUpRepository import com.android.systemui.statusbar.notification.data.repository.HeadsUpRowRepository import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey @@ -29,13 +33,21 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -class HeadsUpNotificationInteractor @Inject constructor(private val repository: HeadsUpRepository) { +class HeadsUpNotificationInteractor +@Inject +constructor( + private val headsUpRepository: HeadsUpRepository, + private val faceAuthInteractor: DeviceEntryFaceAuthInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor, + private val shadeInteractor: ShadeInteractor, +) { - val topHeadsUpRow: Flow<HeadsUpRowKey?> = repository.topHeadsUpRow + val topHeadsUpRow: Flow<HeadsUpRowKey?> = headsUpRepository.topHeadsUpRow /** Set of currently pinned top-level heads up rows to be displayed. */ val pinnedHeadsUpRows: Flow<Set<HeadsUpRowKey>> = - repository.activeHeadsUpRows.flatMapLatest { repositories -> + headsUpRepository.activeHeadsUpRows.flatMapLatest { repositories -> if (repositories.isNotEmpty()) { val toCombine: List<Flow<Pair<HeadsUpRowRepository, Boolean>>> = repositories.map { repo -> repo.isPinned.map { isPinned -> repo to isPinned } } @@ -50,7 +62,7 @@ class HeadsUpNotificationInteractor @Inject constructor(private val repository: /** Are there any pinned heads up rows to display? */ val hasPinnedRows: Flow<Boolean> = - repository.activeHeadsUpRows.flatMapLatest { rows -> + headsUpRepository.activeHeadsUpRows.flatMapLatest { rows -> if (rows.isNotEmpty()) { combine(rows.map { it.isPinned }) { pins -> pins.any { it } } } else { @@ -60,15 +72,38 @@ class HeadsUpNotificationInteractor @Inject constructor(private val repository: } val isHeadsUpOrAnimatingAway: Flow<Boolean> = - combine(hasPinnedRows, repository.isHeadsUpAnimatingAway) { hasPinnedRows, animatingAway -> + combine(hasPinnedRows, headsUpRepository.isHeadsUpAnimatingAway) { + hasPinnedRows, + animatingAway -> hasPinnedRows || animatingAway } + private val canShowHeadsUp: Flow<Boolean> = + combine( + faceAuthInteractor.isBypassEnabled, + shadeInteractor.isShadeFullyCollapsed, + keyguardTransitionInteractor.currentKeyguardState, + notificationsKeyguardInteractor.areNotificationsFullyHidden, + ) { isBypassEnabled, isShadeCollapsed, keyguardState, areNotificationsHidden -> + val isOnLockScreen = keyguardState == KeyguardState.LOCKSCREEN + when { + areNotificationsHidden -> false // don't show when notification are hidden + !isShadeCollapsed -> false // don't show when the shade is expanded + isOnLockScreen -> isBypassEnabled // on the lock screen only show for bypass + else -> true // show otherwise + } + } + + val showHeadsUpStatusBar: Flow<Boolean> = + combine(hasPinnedRows, canShowHeadsUp) { hasPinnedRows, canShowHeadsUp -> + hasPinnedRows && canShowHeadsUp + } + fun headsUpRow(key: HeadsUpRowKey): HeadsUpRowInteractor = HeadsUpRowInteractor(key as HeadsUpRowRepository) fun elementKeyFor(key: HeadsUpRowKey) = (key as HeadsUpRowRepository).elementKey fun setHeadsUpAnimatingAway(animatingAway: Boolean) { - repository.setHeadsUpAnimatingAway(animatingAway) + headsUpRepository.setHeadsUpAnimatingAway(animatingAway) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt index 1f7ab962c3f2..42828d99c7e4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractor.kt @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.notification.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.shared.NotificationMinimalismPrototype import javax.inject.Inject import kotlinx.coroutines.flow.StateFlow @@ -36,4 +38,15 @@ constructor( fun setHasFilteredOutSeenNotifications(value: Boolean) { notificationListRepository.hasFilteredOutSeenNotifications.value = value } + + /** Set the entry that is identified as the top unseen notification. */ + fun setTopUnseenNotification(entry: NotificationEntry?) { + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) return + notificationListRepository.topUnseenNotificationKey.value = entry?.key + } + + /** Determine if the given notification is the top unseen notification. */ + fun isTopUnseenNotification(entry: NotificationEntry?): Boolean = + if (NotificationMinimalismPrototype.V2.isUnexpectedlyInLegacyMode()) false + else entry != null && notificationListRepository.topUnseenNotificationKey.value == entry.key } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java index 968b591b9a09..5a616dfd1f63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.footer.ui.view; import static android.graphics.PorterDuff.Mode.SRC_ATOP; -import static com.android.systemui.Flags.notificationBackgroundTintOptimization; +import static com.android.systemui.Flags.notificationFooterBackgroundTintOptimization; import static com.android.systemui.util.ColorUtilKt.hexColorString; import android.annotation.ColorInt; @@ -407,7 +407,7 @@ public class FooterView extends StackScrollerDecorView { final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background); final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background); final @ColorInt int scHigh; - if (!notificationBackgroundTintOptimization()) { + if (!notificationFooterBackgroundTintOptimization()) { scHigh = Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.materialColorSurfaceContainerHigh); if (scHigh != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt index bfc5932c1db0..87f11f131f32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.interruption +import android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST import android.app.Notification import android.app.Notification.BubbleMetadata import android.app.Notification.CATEGORY_EVENT @@ -23,6 +24,8 @@ import android.app.Notification.CATEGORY_REMINDER import android.app.Notification.VISIBILITY_PRIVATE import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH +import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.database.ContentObserver import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler @@ -234,6 +237,7 @@ class AvalancheSuppressor( private val avalancheProvider: AvalancheProvider, private val systemClock: SystemClock, private val systemSettings: SystemSettings, + private val packageManager: PackageManager, ) : VisualInterruptionFilter( types = setOf(PEEK, PULSE), @@ -241,9 +245,6 @@ class AvalancheSuppressor( ) { val TAG = "AvalancheSuppressor" - override var reason: String = "avalanche" - protected set - enum class State { ALLOW_CONVERSATION_AFTER_AVALANCHE, ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME, @@ -252,24 +253,21 @@ class AvalancheSuppressor( ALLOW_CATEGORY_EVENT, ALLOW_FSI_WITH_PERMISSION_ON, ALLOW_COLORIZED, + ALLOW_EMERGENCY, SUPPRESS } override fun shouldSuppress(entry: NotificationEntry): Boolean { if (!isCooldownEnabled()) { - reason = "FALSE avalanche cooldown setting DISABLED" return false } val timeSinceAvalancheMs = systemClock.currentTimeMillis() - avalancheProvider.startTime val timedOut = timeSinceAvalancheMs >= avalancheProvider.timeoutMs if (timedOut) { - reason = "FALSE avalanche event TIMED OUT. " + - "${timeSinceAvalancheMs/1000} seconds since last avalanche" return false } val state = calculateState(entry) if (state != State.SUPPRESS) { - reason = "FALSE avalanche IN ALLOWLIST: $state" return false } return true @@ -306,13 +304,20 @@ class AvalancheSuppressor( if (entry.sbn.notification.isColorized) { return State.ALLOW_COLORIZED } + if (entry.sbn.notification.isColorized) { + return State.ALLOW_COLORIZED + } + if ( + packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) == + PERMISSION_GRANTED + ) { + return State.ALLOW_EMERGENCY + } return State.SUPPRESS } private fun isCooldownEnabled(): Boolean { - return systemSettings.getInt( - Settings.System.NOTIFICATION_COOLDOWN_ENABLED, - /* def */ 1 - ) == 1 + return systemSettings.getInt(Settings.System.NOTIFICATION_COOLDOWN_ENABLED, /* def */ 1) == + 1 } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt index e6d97c211dc5..f68e194aace2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.statusbar.notification.interruption +import android.content.pm.PackageManager import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler import android.os.PowerManager @@ -63,7 +64,8 @@ constructor( private val uiEventLogger: UiEventLogger, private val userTracker: UserTracker, private val avalancheProvider: AvalancheProvider, - private val systemSettings: SystemSettings + private val systemSettings: SystemSettings, + private val packageManager: PackageManager ) : VisualInterruptionDecisionProvider { init { @@ -172,7 +174,9 @@ constructor( addFilter(AlertKeyguardVisibilitySuppressor(keyguardNotificationVisibilityProvider)) if (NotificationAvalancheSuppression.isEnabled) { - addFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) + addFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) avalancheProvider.register() } started = true @@ -232,14 +236,17 @@ constructor( private fun makeLoggablePeekDecision(entry: NotificationEntry): LoggableDecision = checkConditions(PEEK) - ?: checkFilters(PEEK, entry) ?: checkSuppressInterruptions(entry) - ?: checkSuppressAwakeInterruptions(entry) ?: checkSuppressAwakeHeadsUp(entry) - ?: LoggableDecision.unsuppressed + ?: checkFilters(PEEK, entry) + ?: checkSuppressInterruptions(entry) + ?: checkSuppressAwakeInterruptions(entry) + ?: checkSuppressAwakeHeadsUp(entry) + ?: LoggableDecision.unsuppressed private fun makeLoggablePulseDecision(entry: NotificationEntry): LoggableDecision = checkConditions(PULSE) - ?: checkFilters(PULSE, entry) ?: checkSuppressInterruptions(entry) - ?: LoggableDecision.unsuppressed + ?: checkFilters(PULSE, entry) + ?: checkSuppressInterruptions(entry) + ?: LoggableDecision.unsuppressed override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision = traceSection("VisualInterruptionDecisionProviderImpl#makeAndLogBubbleDecision") { @@ -252,8 +259,10 @@ constructor( private fun makeLoggableBubbleDecision(entry: NotificationEntry): LoggableDecision = checkConditions(BUBBLE) - ?: checkFilters(BUBBLE, entry) ?: checkSuppressInterruptions(entry) - ?: checkSuppressAwakeInterruptions(entry) ?: LoggableDecision.unsuppressed + ?: checkFilters(BUBBLE, entry) + ?: checkSuppressInterruptions(entry) + ?: checkSuppressAwakeInterruptions(entry) + ?: LoggableDecision.unsuppressed private fun logDecision( type: VisualInterruptionType, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java index 89aa3ab9c84d..9e0dd8fc4d92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationPanelLogger.java @@ -21,6 +21,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationPrio import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_HEADS_UP; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PEOPLE; +import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_PRIORITY_PEOPLE; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import android.annotation.Nullable; @@ -130,7 +131,8 @@ public interface NotificationPanelLogger { case BUCKET_HEADS_UP: return Notifications.Notification.SECTION_HEADS_UP; case BUCKET_FOREGROUND_SERVICE: return Notifications.Notification.SECTION_FOREGROUND_SERVICE; - case BUCKET_PEOPLE: return Notifications.Notification.SECTION_PEOPLE; + case BUCKET_PEOPLE, BUCKET_PRIORITY_PEOPLE: + return Notifications.Notification.SECTION_PEOPLE; case BUCKET_ALERTING: return Notifications.Notification.SECTION_ALERTING; case BUCKET_SILENT: return Notifications.Notification.SECTION_SILENT; } 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 61cdea190a43..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; @@ -38,10 +40,12 @@ import com.android.internal.jank.InteractionJankMonitor.Configuration; import com.android.settingslib.Utils; import com.android.systemui.Gefingerpoken; import com.android.systemui.res.R; +import com.android.systemui.shade.TouchLogger; 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; @@ -353,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(); @@ -377,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) { @@ -405,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); @@ -417,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) { @@ -425,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; @@ -507,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; @@ -523,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) { @@ -539,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) { @@ -745,6 +808,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + return TouchLogger.logDispatchTouch( + getClass().getSimpleName(), ev, super.dispatchTouchEvent(ev)); + } + /** * SourceType which should be reset when this View is detached * @param sourceType will be reset on View detached 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 5e3df7b5e60f..edd2961fe119 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 @@ -101,6 +101,7 @@ import com.android.systemui.statusbar.notification.logging.NotificationCounters; import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation; +import com.android.systemui.statusbar.notification.row.wrapper.NotificationCompactMessagingTemplateViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization; import com.android.systemui.statusbar.notification.stack.AmbientState; @@ -375,6 +376,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView }; private OnClickListener mOnClickListener; + @Nullable + private OnClickListener mBubbleClickListener; private OnDragSuccessListener mOnDragSuccessListener; private boolean mHeadsupDisappearRunning; private View mChildAfterViewWhenDismissed; @@ -588,7 +591,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mMenuRow.setAppName(mAppName); } if (mIsSummaryWithChildren) { - if (!AsyncGroupHeaderViewInflation.isEnabled()) { + if (AsyncGroupHeaderViewInflation.isEnabled()) { + mChildrenContainer.updateGroupHeaderExpandState(); + } else { // We create the header from the background thread instead mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation()); @@ -841,6 +846,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } /** + * + * @return true when compact version of Heads Up is on the screen. + */ + public boolean isCompactConversationHeadsUpOnScreen() { + final NotificationViewWrapper viewWrapper = + getVisibleNotificationViewWrapper(); + + return viewWrapper instanceof NotificationCompactMessagingTemplateViewWrapper; + } + /** * @see NotificationChildrenContainer#setUntruncatedChildCount(int) */ public void setUntruncatedChildCount(int childCount) { @@ -1234,14 +1249,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView /** * The click listener for the bubble button. */ + @Nullable public View.OnClickListener getBubbleClickListener() { - return v -> { - if (mBubblesManagerOptional.isPresent()) { - mBubblesManagerOptional.get() - .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */); - } - mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */); - }; + return mBubbleClickListener; + } + + /** + * Sets the click listener for the bubble button. + */ + public void setBubbleClickListener(@Nullable OnClickListener l) { + mBubbleClickListener = l; + // ensure listener is passed to the content views + mPrivateLayout.updateBubbleButton(mEntry); + mPublicLayout.updateBubbleButton(mEntry); } /** @@ -2781,7 +2801,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } - protected void expandNotification() { + /** + * Triggers expand click listener to expand the notification. + */ + public void expandNotification() { mExpandClickListener.onClick(this); } @@ -3069,7 +3092,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) { @@ -3085,7 +3108,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(); @@ -3093,7 +3116,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..9d0fcd3acceb 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,8 @@ package com.android.systemui.statusbar.notification.row import android.app.Flags +import android.os.SystemProperties +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import javax.inject.Inject /** @@ -27,11 +29,19 @@ 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 { + return Flags.compactHeadsUpNotification() && (isInImmersiveMode() || alwaysShow()) + } + + private fun isInImmersiveMode() = + statusBarModeRepositoryStore.defaultDisplay.isInFullscreenMode.value + + /** developer setting to always show Minimal HUN, even if the device is not in full screen */ + private fun alwaysShow() = + SystemProperties.getBoolean("persist.compact_heads_up_notification.always_show", false) } 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/row/ui/viewbinder/ActivatableNotificationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt index 9a54de1481a0..2527af87728e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ui/viewbinder/ActivatableNotificationViewBinder.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.row.ui.viewbinder +import android.util.Log import android.view.MotionEvent import android.view.View import android.view.View.OnTouchListener @@ -72,7 +73,6 @@ private class TouchHandler( var isTouchEnabled = false override fun onTouch(v: View, ev: MotionEvent): Boolean { - val result = false if (ev.action == MotionEvent.ACTION_UP) { view.setLastActionUpTime(ev.eventTime) } @@ -82,13 +82,22 @@ private class TouchHandler( } if (ev.action == MotionEvent.ACTION_UP) { // If this is a false tap, capture the even so it doesn't result in a click. - return falsingManager.isFalseTap(FalsingManager.LOW_PENALTY) + return falsingManager.isFalseTap(FalsingManager.LOW_PENALTY).also { + if (it) { + Log.d(v::class.simpleName ?: TAG, "capturing false tap") + } + } } - return result + return false } override fun onInterceptTouchEvent(ev: MotionEvent): Boolean = false /** Use [onTouch] instead. */ override fun onTouchEvent(ev: MotionEvent): Boolean = false + + companion object { + private const val TAG = "ActivatableNotificationViewBinder" + } } + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt index ce87d2f46d90..3a5f3b201c97 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactHeadsUpTemplateViewWrapper.kt @@ -24,7 +24,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow /** * Compact Heads up Notifications template that doesn't set feedback icon and audibly alert icons */ -class NotificationCompactHeadsUpTemplateViewWrapper( +open class NotificationCompactHeadsUpTemplateViewWrapper( ctx: Context, view: View, row: ExpandableNotificationRow diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt new file mode 100644 index 000000000000..20f04f950fb9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCompactMessagingTemplateViewWrapper.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.notification.row.wrapper + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import com.android.internal.R +import com.android.internal.widget.CachingIconView +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow + +/** Wraps a notification containing a messaging or conversation template */ +class NotificationCompactMessagingTemplateViewWrapper +constructor(ctx: Context, view: View, row: ExpandableNotificationRow) : + NotificationCompactHeadsUpTemplateViewWrapper(ctx, view, row) { + + private val compactMessagingView: ViewGroup = requireNotNull(view as? ViewGroup) + + private var conversationIconView: CachingIconView? = null + private var expandBtn: View? = null + private var titleView: View? = null + private var headerTextSecondary: View? = null + private var subText: View? = null + private var facePileTop: View? = null + private var facePileBottom: View? = null + private var facePileBottomBg: View? = null + override fun onContentUpdated(row: ExpandableNotificationRow?) { + resolveViews() + super.onContentUpdated(row) + } + + private fun resolveViews() { + conversationIconView = compactMessagingView.requireViewById(R.id.conversation_icon) + titleView = compactMessagingView.findViewById(R.id.title) + headerTextSecondary = compactMessagingView.findViewById(R.id.header_text_secondary) + subText = compactMessagingView.findViewById(R.id.header_text) + facePileTop = compactMessagingView.findViewById(R.id.conversation_face_pile_top) + facePileBottom = compactMessagingView.findViewById(R.id.conversation_face_pile_bottom) + facePileBottomBg = + compactMessagingView.findViewById(R.id.conversation_face_pile_bottom_background) + + expandBtn = compactMessagingView.requireViewById(R.id.expand_button) + } + + override fun updateTransformedTypes() { + super.updateTransformedTypes() + + addViewsTransformingToSimilar( + conversationIconView, + titleView, + headerTextSecondary, + subText, + facePileTop, + facePileBottom, + facePileBottomBg, + expandBtn, + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 4244542f1f61..22b95efa5a95 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -74,6 +74,8 @@ public abstract class NotificationViewWrapper implements TransformableView { return new NotificationCallTemplateViewWrapper(ctx, v, row); } else if ("compactHUN".equals((v.getTag()))) { return new NotificationCompactHeadsUpTemplateViewWrapper(ctx, v, row); + } else if ("compactMessagingHUN".equals((v.getTag()))) { + return new NotificationCompactMessagingTemplateViewWrapper(ctx, v, row); } if (row.getEntry().getSbn().getNotification().isStyle( 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..d6c73a9dda9e 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 @@ -23,8 +23,8 @@ import com.android.systemui.flags.RefactorFlagUtils /** Helper for reading or using the heads-up cycling flag state. */ @Suppress("NOTHING_TO_INLINE") object NotificationHeadsUpCycling { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_NOTIFICATION_HEADS_UP_CYCLING + /** The aconfig flag name - enable this feature when FLAG_NOTIFICATION_THROTTLE_HUN is on. */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +33,12 @@ object NotificationHeadsUpCycling { /** Is the heads-up cycling animation enabled */ @JvmStatic inline val isEnabled - get() = Flags.notificationContentAlphaOptimization() + get() = Flags.notificationThrottleHun() + + /** 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..bf37036ee018 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationMinimalismPrototype.kt @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 { + + val version: Int by lazy { + SystemProperties.getInt("persist.notification_minimalism_prototype.version", 2) + } + + 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() && version == 1 + + /** + * 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) + } + object V2 { + 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() && version == 2 + + /** + * The prototype will (by default) use a promoter to ensure that the top unseen notification + * is not grouped, but this property read allows that behavior to be disabled. + */ + val ungroupTopUnseen: Boolean + get() = + if (isUnexpectedlyInLegacyMode()) false + else + SystemProperties.getBoolean( + "persist.notification_minimalism_prototype.ungroup_top_unseen", + true + ) + + /** + * 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/shared/PriorityPeopleSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.kt new file mode 100644 index 000000000000..472fd9564963 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/PriorityPeopleSection.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.statusbar.notification.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for com.android.systemui.Flags.FLAG_PRIORITY_PEOPLE_SECTION */ +@Suppress("NOTHING_TO_INLINE") +object PriorityPeopleSection { + const val FLAG_NAME = Flags.FLAG_PRIORITY_PEOPLE_SECTION + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Are sections sorted by time? */ + @JvmStatic + inline val isEnabled + get() = Flags.priorityPeopleSection() + + /** + * 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/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index 92c597cf384e..48796d8f8f2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -439,6 +439,15 @@ public class NotificationChildrenContainer extends ViewGroup Trace.endSection(); } + /** + * Update the expand state of the group header. + */ + public void updateGroupHeaderExpandState() { + if (mGroupHeaderWrapper != null) { + mGroupHeaderWrapper.setExpanded(mChildrenExpanded); + } + } + private void removeGroupHeader() { if (mGroupHeader == null) { return; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt index 31f4857e4b04..fc28a99ef4ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt @@ -3,15 +3,22 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.IntDef /** - * For now, declare the available notification buckets (sections) here so that other - * presentation code can decide what to do based on an entry's buckets + * For now, declare the available notification buckets (sections) here so that other presentation + * code can decide what to do based on an entry's buckets */ @Retention(AnnotationRetention.SOURCE) @IntDef( - prefix = ["BUCKET_"], - value = [ - BUCKET_UNKNOWN, BUCKET_MEDIA_CONTROLS, BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, - BUCKET_PEOPLE, BUCKET_ALERTING, BUCKET_SILENT + prefix = ["BUCKET_"], + value = + [ + BUCKET_UNKNOWN, + BUCKET_MEDIA_CONTROLS, + BUCKET_HEADS_UP, + BUCKET_FOREGROUND_SERVICE, + BUCKET_PRIORITY_PEOPLE, + BUCKET_PEOPLE, + BUCKET_ALERTING, + BUCKET_SILENT ] ) annotation class PriorityBucket @@ -20,6 +27,7 @@ const val BUCKET_UNKNOWN = 0 const val BUCKET_MEDIA_CONTROLS = 1 const val BUCKET_HEADS_UP = 2 const val BUCKET_FOREGROUND_SERVICE = 3 +const val BUCKET_PRIORITY_PEOPLE = 7 const val BUCKET_PEOPLE = 4 const val BUCKET_ALERTING = 5 const val BUCKET_SILENT = 6 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java index 2d0395a2f606..5a433a1f1a04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSection.java @@ -16,17 +16,6 @@ package com.android.systemui.statusbar.notification.stack; -import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_MEDIA_CONTROLS; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; -import android.animation.PropertyValuesHolder; -import android.graphics.Rect; -import android.view.View; -import android.view.animation.Interpolator; - -import com.android.app.animation.Interpolators; import com.android.systemui.statusbar.notification.row.ExpandableView; /** @@ -35,165 +24,18 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; */ public class NotificationSection { private @PriorityBucket final int mBucket; - private final View mOwningView; - private final Rect mBounds = new Rect(); - private final Rect mCurrentBounds = new Rect(-1, -1, -1, -1); - private final Rect mStartAnimationRect = new Rect(); - private final Rect mEndAnimationRect = new Rect(); - private ObjectAnimator mTopAnimator = null; - private ObjectAnimator mBottomAnimator = null; private ExpandableView mFirstVisibleChild; private ExpandableView mLastVisibleChild; - NotificationSection(View owningView, @PriorityBucket int bucket) { - mOwningView = owningView; + NotificationSection(@PriorityBucket int bucket) { mBucket = bucket; } - public void cancelAnimators() { - if (mBottomAnimator != null) { - mBottomAnimator.cancel(); - } - if (mTopAnimator != null) { - mTopAnimator.cancel(); - } - } - - public Rect getCurrentBounds() { - return mCurrentBounds; - } - - public Rect getBounds() { - return mBounds; - } - - public boolean didBoundsChange() { - return !mCurrentBounds.equals(mBounds); - } - - public boolean areBoundsAnimating() { - return mBottomAnimator != null || mTopAnimator != null; - } - @PriorityBucket public int getBucket() { return mBucket; } - public void startBackgroundAnimation(boolean animateTop, boolean animateBottom) { - // Left and right bounds are always applied immediately. - mCurrentBounds.left = mBounds.left; - mCurrentBounds.right = mBounds.right; - startBottomAnimation(animateBottom); - startTopAnimation(animateTop); - } - - - private void startTopAnimation(boolean animate) { - int previousEndValue = mEndAnimationRect.top; - int newEndValue = mBounds.top; - ObjectAnimator previousAnimator = mTopAnimator; - if (previousAnimator != null && previousEndValue == newEndValue) { - return; - } - if (!animate) { - // just a local update was performed - if (previousAnimator != null) { - // we need to increase all animation keyframes of the previous animator by the - // relative change to the end value - int previousStartValue = mStartAnimationRect.top; - PropertyValuesHolder[] values = previousAnimator.getValues(); - values[0].setIntValues(previousStartValue, newEndValue); - mStartAnimationRect.top = previousStartValue; - mEndAnimationRect.top = newEndValue; - previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); - return; - } else { - // no new animation needed, let's just apply the value - setBackgroundTop(newEndValue); - return; - } - } - if (previousAnimator != null) { - previousAnimator.cancel(); - } - ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop", - mCurrentBounds.top, newEndValue); - Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN; - animator.setInterpolator(interpolator); - animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - // remove the tag when the animation is finished - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mStartAnimationRect.top = -1; - mEndAnimationRect.top = -1; - mTopAnimator = null; - } - }); - animator.start(); - mStartAnimationRect.top = mCurrentBounds.top; - mEndAnimationRect.top = newEndValue; - mTopAnimator = animator; - } - - private void startBottomAnimation(boolean animate) { - int previousStartValue = mStartAnimationRect.bottom; - int previousEndValue = mEndAnimationRect.bottom; - int newEndValue = mBounds.bottom; - ObjectAnimator previousAnimator = mBottomAnimator; - if (previousAnimator != null && previousEndValue == newEndValue) { - return; - } - if (!animate) { - // just a local update was performed - if (previousAnimator != null) { - // we need to increase all animation keyframes of the previous animator by the - // relative change to the end value - PropertyValuesHolder[] values = previousAnimator.getValues(); - values[0].setIntValues(previousStartValue, newEndValue); - mStartAnimationRect.bottom = previousStartValue; - mEndAnimationRect.bottom = newEndValue; - previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); - return; - } else { - // no new animation needed, let's just apply the value - setBackgroundBottom(newEndValue); - return; - } - } - if (previousAnimator != null) { - previousAnimator.cancel(); - } - ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom", - mCurrentBounds.bottom, newEndValue); - Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN; - animator.setInterpolator(interpolator); - animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); - // remove the tag when the animation is finished - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mStartAnimationRect.bottom = -1; - mEndAnimationRect.bottom = -1; - mBottomAnimator = null; - } - }); - animator.start(); - mStartAnimationRect.bottom = mCurrentBounds.bottom; - mEndAnimationRect.bottom = newEndValue; - mBottomAnimator = animator; - } - - private void setBackgroundTop(int top) { - mCurrentBounds.top = top; - mOwningView.invalidate(); - } - - private void setBackgroundBottom(int bottom) { - mCurrentBounds.bottom = bottom; - mOwningView.invalidate(); - } public ExpandableView getFirstVisibleChild() { return mFirstVisibleChild; @@ -215,91 +57,4 @@ public class NotificationSection { return changed; } - public void resetCurrentBounds() { - mCurrentBounds.set(mBounds); - } - - /** - * Returns true if {@code top} is equal to the top of this section (if not currently animating) - * or where the top of this section will be when animation completes. - */ - public boolean isTargetTop(int top) { - return (mTopAnimator == null && mCurrentBounds.top == top) - || (mTopAnimator != null && mEndAnimationRect.top == top); - } - - /** - * Returns true if {@code bottom} is equal to the bottom of this section (if not currently - * animating) or where the bottom of this section will be when animation completes. - */ - public boolean isTargetBottom(int bottom) { - return (mBottomAnimator == null && mCurrentBounds.bottom == bottom) - || (mBottomAnimator != null && mEndAnimationRect.bottom == bottom); - } - - /** - * Update the bounds of this section based on it's views - * - * @param minTopPosition the minimum position that the top needs to have - * @param minBottomPosition the minimum position that the bottom needs to have - * @return the position of the new bottom - */ - public int updateBounds(int minTopPosition, int minBottomPosition, - boolean shiftBackgroundWithFirst) { - int top = minTopPosition; - int bottom = minTopPosition; - ExpandableView firstView = getFirstVisibleChild(); - if (firstView != null) { - // Round Y up to avoid seeing the background during animation - int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView)); - // TODO: look into the already animating part - int newTop; - if (isTargetTop(finalTranslationY)) { - // we're ending up at the same location as we are now, let's just skip the - // animation - newTop = finalTranslationY; - } else { - newTop = (int) Math.ceil(firstView.getTranslationY()); - } - top = Math.max(newTop, top); - if (firstView.showingPulsing()) { - // If we're pulsing, the notification can actually go below! - bottom = Math.max(bottom, finalTranslationY - + ExpandableViewState.getFinalActualHeight(firstView)); - if (shiftBackgroundWithFirst) { - mBounds.left += Math.max(firstView.getTranslation(), 0); - mBounds.right += Math.min(firstView.getTranslation(), 0); - } - } - } - ExpandableView lastView = getLastVisibleChild(); - if (lastView != null) { - float finalTranslationY = ViewState.getFinalTranslationY(lastView); - int finalHeight = ExpandableViewState.getFinalActualHeight(lastView); - // Round Y down to avoid seeing the background during animation - int finalBottom = (int) Math.floor( - finalTranslationY + finalHeight - lastView.getClipBottomAmount()); - int newBottom; - if (isTargetBottom(finalBottom)) { - // we're ending up at the same location as we are now, lets just skip the animation - newBottom = finalBottom; - } else { - newBottom = (int) (lastView.getTranslationY() + lastView.getActualHeight() - - lastView.getClipBottomAmount()); - // The background can never be lower than the end of the last view - minBottomPosition = (int) Math.min( - lastView.getTranslationY() + lastView.getActualHeight(), - minBottomPosition); - } - bottom = Math.max(bottom, Math.max(newBottom, minBottomPosition)); - } - bottom = Math.max(top, bottom); - mBounds.top = top; - mBounds.bottom = bottom; - return bottom; - } - - public boolean needsBackground() { - return mFirstVisibleChild != null && mBucket != BUCKET_MEDIA_CONTROLS; - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt index d269eda6795a..3400ad107133 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt @@ -40,7 +40,9 @@ import javax.inject.Inject * * TODO: Move remaining sections logic from NSSL into this class. */ -class NotificationSectionsManager @Inject internal constructor( +class NotificationSectionsManager +@Inject +internal constructor( private val configurationController: ConfigurationController, private val keyguardMediaController: KeyguardMediaController, private val sectionsFeatureManager: NotificationSectionsFeatureManager, @@ -52,11 +54,12 @@ class NotificationSectionsManager @Inject internal constructor( @SilentHeader private val silentHeaderController: SectionHeaderController ) : SectionProvider { - private val configurationListener = object : ConfigurationController.ConfigurationListener { - override fun onLocaleListChanged() { - reinflateViews() + private val configurationListener = + object : ConfigurationController.ConfigurationListener { + override fun onLocaleListChanged() { + reinflateViews() + } } - } private lateinit var parent: NotificationStackScrollLayout private var initialized = false @@ -81,7 +84,7 @@ class NotificationSectionsManager @Inject internal constructor( val mediaControlsView: MediaContainerView? get() = mediaContainerController.mediaContainerView - /** Must be called before use. */ + /** Must be called before use. */ fun initialize(parent: NotificationStackScrollLayout) { check(!initialized) { "NotificationSectionsManager already initialized" } initialized = true @@ -91,13 +94,12 @@ class NotificationSectionsManager @Inject internal constructor( } fun createSectionsForBuckets(): Array<NotificationSection> = - sectionsFeatureManager.getNotificationBuckets() - .map { NotificationSection(parent, it) } - .toTypedArray() + sectionsFeatureManager + .getNotificationBuckets() + .map { NotificationSection(it) } + .toTypedArray() - /** - * Reinflates the entire notification header, including all decoration views. - */ + /** Reinflates the entire notification header, including all decoration views. */ fun reinflateViews() { silentHeaderController.reinflateView(parent) alertingHeaderController.reinflateView(parent) @@ -108,44 +110,44 @@ class NotificationSectionsManager @Inject internal constructor( } override fun beginsSection(view: View, previous: View?): Boolean = - view === silentHeaderView || + view === silentHeaderView || view === mediaControlsView || view === peopleHeaderView || view === alertingHeaderView || view === incomingHeaderView || getBucket(view) != getBucket(previous) - private fun getBucket(view: View?): Int? = when { - view === silentHeaderView -> BUCKET_SILENT - view === incomingHeaderView -> BUCKET_HEADS_UP - view === mediaControlsView -> BUCKET_MEDIA_CONTROLS - view === peopleHeaderView -> BUCKET_PEOPLE - view === alertingHeaderView -> BUCKET_ALERTING - view is ExpandableNotificationRow -> view.entry.bucket - else -> null - } + private fun getBucket(view: View?): Int? = + when { + view === silentHeaderView -> BUCKET_SILENT + view === incomingHeaderView -> BUCKET_HEADS_UP + view === mediaControlsView -> BUCKET_MEDIA_CONTROLS + view === peopleHeaderView -> BUCKET_PEOPLE + view === alertingHeaderView -> BUCKET_ALERTING + view is ExpandableNotificationRow -> view.entry.bucket + else -> null + } private sealed class SectionBounds { - data class Many( - val first: ExpandableView, - val last: ExpandableView - ) : SectionBounds() + data class Many(val first: ExpandableView, val last: ExpandableView) : SectionBounds() data class One(val lone: ExpandableView) : SectionBounds() object None : SectionBounds() - fun addNotif(notif: ExpandableView): SectionBounds = when (this) { - is None -> One(notif) - is One -> Many(lone, notif) - is Many -> copy(last = notif) - } + fun addNotif(notif: ExpandableView): SectionBounds = + when (this) { + is None -> One(notif) + is One -> Many(lone, notif) + is Many -> copy(last = notif) + } - fun updateSection(section: NotificationSection): Boolean = when (this) { - is None -> section.setFirstAndLastVisibleChildren(null, null) - is One -> section.setFirstAndLastVisibleChildren(lone, lone) - is Many -> section.setFirstAndLastVisibleChildren(first, last) - } + fun updateSection(section: NotificationSection): Boolean = + when (this) { + is None -> section.setFirstAndLastVisibleChildren(null, null) + is One -> section.setFirstAndLastVisibleChildren(lone, lone) + is Many -> section.setFirstAndLastVisibleChildren(first, last) + } private fun NotificationSection.setFirstAndLastVisibleChildren( first: ExpandableView?, @@ -167,17 +169,19 @@ class NotificationSectionsManager @Inject internal constructor( children: List<ExpandableView> ): Boolean { // Create mapping of bucket to section - val sectionBounds = children.asSequence() + val sectionBounds = + children + .asSequence() // Group children by bucket .groupingBy { getBucket(it) - ?: throw IllegalArgumentException("Cannot find section bucket for view") + ?: throw IllegalArgumentException("Cannot find section bucket for view") } // Combine each bucket into a SectionBoundary .foldToSparseArray( - SectionBounds.None, - size = sections.size, - operation = SectionBounds::addNotif + SectionBounds.None, + size = sections.size, + operation = SectionBounds::addNotif ) // Build a set of the old first/last Views of the sections @@ -185,11 +189,12 @@ class NotificationSectionsManager @Inject internal constructor( val oldLastChildren = sections.mapNotNull { it.lastVisibleChild }.toSet().toMutableSet() // Update each section with the associated boundary, tracking if there was a change - val changed = sections.fold(false) { changed, section -> - val bounds = sectionBounds[section.bucket] ?: SectionBounds.None - val isSectionChanged = bounds.updateSection(section) - isSectionChanged || changed - } + val changed = + sections.fold(false) { changed, section -> + val bounds = sectionBounds[section.bucket] ?: SectionBounds.None + val isSectionChanged = bounds.updateSection(section) + isSectionChanged || changed + } val newFirstChildren = sections.mapNotNull { it.firstVisibleChild } val newLastChildren = sections.mapNotNull { it.lastVisibleChild } @@ -229,16 +234,18 @@ class NotificationSectionsManager @Inject internal constructor( private fun logSections(sections: Array<NotificationSection>) { for (i in sections.indices) { val s = sections[i] - val fs = when (val first = s.firstVisibleChild) { - null -> "(null)" - is ExpandableNotificationRow -> first.entry.key - else -> Integer.toHexString(System.identityHashCode(first)) - } - val ls = when (val last = s.lastVisibleChild) { - null -> "(null)" - is ExpandableNotificationRow -> last.entry.key - else -> Integer.toHexString(System.identityHashCode(last)) - } + val fs = + when (val first = s.firstVisibleChild) { + null -> "(null)" + is ExpandableNotificationRow -> first.entry.key + else -> Integer.toHexString(System.identityHashCode(first)) + } + val ls = + when (val last = s.lastVisibleChild) { + null -> "(null)" + is ExpandableNotificationRow -> last.entry.key + else -> Integer.toHexString(System.identityHashCode(last)) + } Log.d(TAG, "updateSections: f=$fs s=$i") Log.d(TAG, "updateSections: l=$ls s=$i") } 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 57504b2649bf..a9d7cc003b79 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; @@ -126,8 +127,8 @@ import com.android.systemui.statusbar.policy.ScrollAdapter; import com.android.systemui.statusbar.policy.SplitShadeStateController; import com.android.systemui.util.Assert; import com.android.systemui.util.ColorUtilKt; -import com.android.systemui.util.Compile; import com.android.systemui.util.DumpUtilsKt; +import com.android.systemui.util.ListenerSet; import com.google.errorprone.annotations.CompileTimeConstant; @@ -152,11 +153,9 @@ 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); - private static final boolean DEBUG_UPDATE_SIDE_PADDING = Compile.IS_DEBUG; private boolean mShadeNeedsToClose = false; @@ -256,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; @@ -318,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; } @@ -674,7 +674,7 @@ public class NotificationStackScrollLayout void setOverExpansion(float margin) { mAmbientState.setOverExpansion(margin); if (notificationOverExpansionClippingFix() && !SceneContainerFlag.isEnabled()) { - setRoundingClippingYTranslation((int) margin); + setRoundingClippingYTranslation(mShouldUseSplitNotificationShade ? (int) margin : 0); } updateStackPosition(); requestChildrenUpdate(); @@ -933,11 +933,6 @@ public class NotificationStackScrollLayout + " mSkinnyNotifsInLandscape=" + mSkinnyNotifsInLandscape; mLastInitViewElapsedRealtime = SystemClock.elapsedRealtime(); - if (DEBUG_UPDATE_SIDE_PADDING) { - Log.v(TAG, "initView @ elapsedRealtime " + mLastInitViewElapsedRealtime + ": " - + mLastInitViewDumpString); - } - mGapHeight = res.getDimensionPixelSize(R.dimen.notification_section_divider_height); mStackScrollAlgorithm.initView(context); mStateAnimator.initView(context); @@ -967,12 +962,6 @@ public class NotificationStackScrollLayout + " orientation=" + orientation; mLastUpdateSidePaddingElapsedRealtime = SystemClock.elapsedRealtime(); - if (DEBUG_UPDATE_SIDE_PADDING) { - Log.v(TAG, - "updateSidePadding @ elapsedRealtime " + mLastUpdateSidePaddingElapsedRealtime - + ": " + mLastUpdateSidePaddingDumpString); - } - if (viewWidth == 0) { Log.e(TAG, "updateSidePadding: viewWidth is zero"); mSidePaddings = mMinimumPaddings; @@ -1096,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(); } @@ -1105,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) { @@ -1123,8 +1132,10 @@ public class NotificationStackScrollLayout (int) height); } } - setMaxLayoutHeight(getHeight()); - updateContentHeight(); + if (!SceneContainerFlag.isEnabled()) { + setMaxLayoutHeight(getHeight()); + updateContentHeight(); + } clampScrollPosition(); requestChildrenUpdate(); updateFirstAndLastBackgroundViews(); @@ -1146,12 +1157,18 @@ public class NotificationStackScrollLayout } } + @NonNull @Override public View asView() { return this; } @Override + public void setMaxAlpha(float alpha) { + mController.setMaxAlphaFromView(alpha); + } + + @Override public void setScrolledToTop(boolean scrolledToTop) { mScrollViewFields.setScrolledToTop(scrolledToTop); } @@ -1179,8 +1196,8 @@ public class NotificationStackScrollLayout } @Override - public void setStackHeightConsumer(@Nullable Consumer<Float> consumer) { - mScrollViewFields.setStackHeightConsumer(consumer); + public void setCurrentGestureOverscrollConsumer(@Nullable Consumer<Boolean> consumer) { + mScrollViewFields.setCurrentGestureOverscrollConsumer(consumer); } @Override @@ -1471,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(); } @@ -2405,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(); } /** @@ -2947,7 +2974,7 @@ public class NotificationStackScrollLayout private void updateFirstAndLastBackgroundViews() { ExpandableView lastChild = getLastChildWithBackground(); - boolean sectionViewsChanged = mSectionsManager.updateFirstAndLastViewsForAllSections( + mSectionsManager.updateFirstAndLastViewsForAllSections( mSections, getChildrenWithBackground()); mAmbientState.setLastVisibleBackgroundChild(lastChild); @@ -3145,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); @@ -3165,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; @@ -3396,14 +3433,21 @@ 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(); } @@ -3422,6 +3466,14 @@ public class NotificationStackScrollLayout downEvent.recycle(); } + // Only when scene container is enabled, mark that we are being dragged so that we start + // dispatching the rest of the gesture to scene container. + void startOverscrollAfterExpanding() { + SceneContainerFlag.isUnexpectedlyInLegacyMode(); + getExpandHelper().finishExpanding(); + setIsBeingDragged(true); + } + @Override public boolean onGenericMotionEvent(MotionEvent event) { if (!isScrollingEnabled() @@ -5539,6 +5591,11 @@ public class NotificationStackScrollLayout return mExpandingNotification; } + @VisibleForTesting + void setExpandingNotification(boolean isExpanding) { + mExpandingNotification = isExpanding; + } + boolean getDisallowScrollingInThisMotion() { return mDisallowScrollingInThisMotion; } @@ -5551,6 +5608,11 @@ public class NotificationStackScrollLayout return mExpandedInThisMotion; } + @VisibleForTesting + void setExpandedInThisMotion(boolean expandedInThisMotion) { + mExpandedInThisMotion = expandedInThisMotion; + } + boolean getDisallowDismissInThisMotion() { return mDisallowDismissInThisMotion; } @@ -6115,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[]{ @@ -6166,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; @@ -6184,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/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 9ed5ac939847..6a3055f4b4cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -23,7 +23,7 @@ import static com.android.app.animation.Interpolators.STANDARD; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.server.notification.Flags.screenshareNotificationHiding; import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME; -import static com.android.systemui.Flags.nsslFalsingFix; +import static com.android.systemui.Flags.confineNotificationTouchToViewWidth; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener; import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener; @@ -206,6 +206,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { private final SeenNotificationsInteractor mSeenNotificationsInteractor; private final KeyguardTransitionRepository mKeyguardTransitionRepo; private NotificationStackScrollLayout mView; + private TouchHandler mTouchHandler; private NotificationSwipeHelper mSwipeHelper; @Nullable private Boolean mHistoryEnabled; @@ -356,6 +357,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { private float mMaxAlphaForKeyguard = 1.0f; private String mMaxAlphaForKeyguardSource = "constructor"; private float mMaxAlphaForUnhide = 1.0f; + private float mMaxAlphaFromView = 1.0f; /** * Maximum alpha when to and from or sitting idle on the glanceable hub. Will be 1.0f when the @@ -596,7 +598,7 @@ public class NotificationStackScrollLayoutController implements Dumpable { ev.getY(), true /* requireMinHeight */, false /* ignoreDecors */, - true /* ignoreWidth */); + !confineNotificationTouchToViewWidth() /* ignoreWidth */); if (child instanceof ExpandableNotificationRow row) { ExpandableNotificationRow parent = row.getNotificationParent(); if (parent != null && parent.areChildrenExpanded() @@ -806,7 +808,8 @@ public class NotificationStackScrollLayoutController implements Dumpable { mView.setStackStateLogger(mStackStateLogger); mView.setController(this); mView.setLogger(mLogger); - mView.setTouchHandler(new TouchHandler()); + mTouchHandler = new TouchHandler(); + mView.setTouchHandler(mTouchHandler); mView.setResetUserExpandedStatesRunnable(mNotificationsController::resetUserExpandedStates); mView.setActivityStarter(mActivityStarter); mView.setClearAllAnimationListener(this::onAnimationEnd); @@ -1317,9 +1320,14 @@ public class NotificationStackScrollLayoutController implements Dumpable { updateAlpha(); } + void setMaxAlphaFromView(float alpha) { + mMaxAlphaFromView = alpha; + updateAlpha(); + } + private void updateAlpha() { if (mView != null) { - mView.setAlpha(Math.min(mMaxAlphaForKeyguard, + mView.setAlpha(Math.min(Math.min(mMaxAlphaFromView, mMaxAlphaForKeyguard), Math.min(mMaxAlphaForUnhide, mMaxAlphaForGlanceableHub))); } } @@ -1787,8 +1795,14 @@ public class NotificationStackScrollLayoutController implements Dumpable { } } + @VisibleForTesting + TouchHandler getTouchHandler() { + return mTouchHandler; + } + @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mMaxAlphaFromView=" + mMaxAlphaFromView); pw.println("mMaxAlphaForUnhide=" + mMaxAlphaForUnhide); pw.println("mMaxAlphaForGlanceableHub=" + mMaxAlphaForGlanceableHub); pw.println("mMaxAlphaForKeyguard=" + mMaxAlphaForKeyguard); @@ -2036,37 +2050,30 @@ public class NotificationStackScrollLayoutController implements Dumpable { expandingNotification = mView.isExpandingNotification(); if (mView.getExpandedInThisMotion() && !expandingNotification && wasExpandingBefore && !mView.getDisallowScrollingInThisMotion()) { - mView.dispatchDownEventToScroller(ev); + // We need to dispatch the overscroll differently when Scene Container is on, + // since NSSL no longer controls its own scroll. + if (SceneContainerFlag.isEnabled() && !isCancelOrUp) { + mView.startOverscrollAfterExpanding(); + return true; + } else { + mView.dispatchDownEventToScroller(ev); + } } } boolean horizontalSwipeWantsIt = false; boolean scrollerWantsIt = false; - if (nsslFalsingFix() || MigrateClocksToBlueprint.isEnabled()) { - // Reverse the order relative to the else statement. onScrollTouch will reset on an - // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes. - if (mLongPressedView == null && !mView.isBeingDragged() - && !expandingNotification - && !mView.getExpandedInThisMotion() - && !onlyScrollingInThisMotion - && !mView.getDisallowDismissInThisMotion()) { - horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); - } - if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping() - && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) { - scrollerWantsIt = mView.onScrollTouch(ev); - } - } else { - if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping() - && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) { - scrollerWantsIt = mView.onScrollTouch(ev); - } - if (mLongPressedView == null && !mView.isBeingDragged() - && !expandingNotification - && !mView.getExpandedInThisMotion() - && !onlyScrollingInThisMotion - && !mView.getDisallowDismissInThisMotion()) { - horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); - } + // NOTE: the order of these is important. If reversed, onScrollTouch will reset on an + // UP event, causing horizontalSwipeWantsIt to be set to true on vertical swipes. + if (mLongPressedView == null && !mView.isBeingDragged() + && !expandingNotification + && !mView.getExpandedInThisMotion() + && !onlyScrollingInThisMotion + && !mView.getDisallowDismissInThisMotion()) { + horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev); + } + if (mLongPressedView == null && mView.isExpanded() && !mSwipeHelper.isSwiping() + && !expandingNotification && !mView.getDisallowScrollingInThisMotion()) { + scrollerWantsIt = mView.onScrollTouch(ev); } // Check if we need to clear any snooze leavebehinds 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..4b0b1e0029f3 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 @@ -73,6 +72,9 @@ constructor( */ private var maxNotificationsExcludesMedia = false + /** Whether we allow keyguard to show less important notifications above the shelf. */ + private var limitLockScreenToImportant = false + /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */ private var dividerHeight by notNull<Float>() @@ -86,6 +88,14 @@ constructor( updateResources() } + private fun allowedByPolicy(stackHeight: StackHeight): Boolean = + if (limitLockScreenToImportant && stackHeight.includesLessImportantNotification) { + log { "\tallowedByPolicy = false" } + false + } else { + true + } + /** * Returns whether notifications and (shelf if visible) can fit in total space available. * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon. @@ -184,11 +194,12 @@ constructor( log { "\tGet maxNotifWithoutSavingSpace ---" } val maxNotifWithoutSavingSpace = stackHeightSequence.lastIndexWhile { heightResult -> - canStackFitInSpace( - heightResult, - notifSpace = notifSpace, - shelfSpace = shelfSpace - ) == FitResult.FIT + allowedByPolicy(heightResult) && + canStackFitInSpace( + heightResult, + notifSpace = notifSpace, + shelfSpace = shelfSpace + ) == FitResult.FIT } // How many notifications we can show at heightWithoutLockscreenConstraints @@ -213,11 +224,12 @@ constructor( saveSpaceOnLockscreen = true maxNotifications = stackHeightSequence.lastIndexWhile { heightResult -> - canStackFitInSpace( - heightResult, - notifSpace = notifSpace, - shelfSpace = shelfSpace - ) != FitResult.NO_FIT + allowedByPolicy(heightResult) && + canStackFitInSpace( + heightResult, + notifSpace = notifSpace, + shelfSpace = shelfSpace + ) != FitResult.NO_FIT } log { "\t--- maxNotifications=$maxNotifications" } } @@ -319,7 +331,10 @@ constructor( // Float height of shelf (0 if shelf is not showing), and space before the shelf that // changes during the lockscreen <=> full shade transition. - val shelfHeightWithSpaceBefore: Float + val shelfHeightWithSpaceBefore: Float, + + /** Whether this stack height includes less at least one important notification. */ + val includesLessImportantNotification: Boolean ) private fun computeHeightPerNotificationLimit( @@ -332,12 +347,15 @@ constructor( var previous: ExpandableView? = null val onLockscreen = onLockscreen() + var includesLessImportantNotification = false + // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState). yield( StackHeight( notifsHeight = 0f, notifsHeightSavingSpace = 0f, - shelfHeightWithSpaceBefore = shelfHeight + shelfHeightWithSpaceBefore = shelfHeight, + includesLessImportantNotification = includesLessImportantNotification, ) ) @@ -363,6 +381,19 @@ constructor( spaceBeforeShelf + shelfHeight } + if (limitLockScreenToImportant && !includesLessImportantNotification) { + val bucket = (currentNotification as? ExpandableNotificationRow)?.entry?.bucket + includesLessImportantNotification = + when (bucket) { + null, + BUCKET_MEDIA_CONTROLS, + BUCKET_HEADS_UP, + BUCKET_FOREGROUND_SERVICE, + BUCKET_PRIORITY_PEOPLE -> false + else -> true + } + } + log { "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " + "notifsHeightSavingSpace=$notifsWithCollapsedHun" + @@ -372,7 +403,8 @@ constructor( StackHeight( notifsHeight = notifications, notifsHeightSavingSpace = notifsWithCollapsedHun, - shelfHeightWithSpaceBefore = shelfWithSpaceBefore + shelfHeightWithSpaceBefore = shelfWithSpaceBefore, + includesLessImportantNotification = includesLessImportantNotification, ) ) } @@ -381,16 +413,18 @@ 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 if (NotificationMinimalismPrototype.V2.isEnabled) { + 1 } else { resources.getInteger(R.integer.keyguard_max_notification_count) } ) - maxNotificationsExcludesMedia = notificationMinimalismPrototype() + maxNotificationsExcludesMedia = + NotificationMinimalismPrototype.V1.isEnabled || + NotificationMinimalismPrototype.V2.isEnabled + limitLockScreenToImportant = NotificationMinimalismPrototype.V2.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 edac5ede1e91..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,16 +45,22 @@ 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 */ var syntheticScrollConsumer: Consumer<Float>? = null /** - * Any time the stack height is recalculated, it should be updated here to be used by the - * placeholder + * When a gesture is consumed internally by NSSL but needs to be handled by other elements (such + * as the notif scrim) as overscroll, we can notify the placeholder through here. */ - var stackHeightConsumer: Consumer<Float>? = null + var currentGestureOverscrollConsumer: Consumer<Boolean>? = null /** * Any time the heads up height is recalculated, it should be updated here to be used by the * placeholder @@ -64,8 +70,9 @@ class ScrollViewFields { /** send the [syntheticScroll] to the [syntheticScrollConsumer], if present. */ fun sendSyntheticScroll(syntheticScroll: Float) = syntheticScrollConsumer?.accept(syntheticScroll) - /** send the [stackHeight] to the [stackHeightConsumer], if present. */ - fun sendStackHeight(stackHeight: Float) = stackHeightConsumer?.accept(stackHeight) + /** send [isCurrentGestureOverscroll] to the [currentGestureOverscrollConsumer], if present. */ + fun sendCurrentGestureOverscroll(isCurrentGestureOverscroll: Boolean) = + currentGestureOverscrollConsumer?.accept(isCurrentGestureOverscroll) /** 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 e980794d23dd..7bbaf8534ae2 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); @@ -169,6 +173,14 @@ public class StackScrollAlgorithm { } } + // On the final call to {@link #resetViewState}, the alpha is set back to 1f but + // ambientState.isExpansionChanging() is now false. This causes a flicker on the + // EmptyShadeView after the shade is collapsed. Make sure the empty shade view + // isn't visible unless the shade is expanded. + if (view instanceof EmptyShadeView && ambientState.getExpansionFraction() == 0f) { + viewState.setAlpha(0f); + } + // For EmptyShadeView if on keyguard, we need to control the alpha to create // a nice transition when the user is dragging down the notification panel. if (view instanceof EmptyShadeView && ambientState.isOnKeyguard()) { @@ -340,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; @@ -362,6 +375,46 @@ 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; + if (row.getEntry() == null) 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; + if (row.getEntry() == null) 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) { @@ -791,6 +844,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)) { @@ -831,6 +885,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(); @@ -852,6 +913,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/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt index b04737936166..db544ce59aa1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt @@ -27,6 +27,10 @@ import kotlinx.coroutines.flow.MutableStateFlow */ @SysUISingleton class NotificationPlaceholderRepository @Inject constructor() { + + /** The alpha of the shade in order to show brightness. */ + val alphaForBrightnessMirror = MutableStateFlow(1f) + /** * The bounds of the notification shade scrim / container in the current scene. * @@ -34,16 +38,6 @@ class NotificationPlaceholderRepository @Inject constructor() { */ val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null) - /** - * The y-coordinate in px of top of the contents of the notification stack. This value can be - * negative, if the stack is scrolled such that its top extends beyond the top edge of the - * screen. - */ - val stackTop = MutableStateFlow(0f) - - /** the bottom-most acceptable y-position for the bottom of the stack / shelf */ - val stackBottom = MutableStateFlow(0f) - /** the y position of the top of the HUN area */ val headsUpTop = MutableStateFlow(0f) 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 8a9da69079d4..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) @@ -43,4 +36,10 @@ class NotificationViewHeightRepository @Inject constructor() { * necessary to scroll up to keep expanding the notification. */ val syntheticScroll = MutableStateFlow(0f) + + /** + * Whether the current touch gesture is overscroll. If true, it means the NSSL has already + * consumed part of the gesture. + */ + val isCurrentGestureOverscroll = MutableStateFlow(false) } 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 a5b4f5f2bb4f..e7acbe3ab0b0 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,21 +65,12 @@ 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() - /** The y-coordinate in px of top of the contents of the notification stack. */ - val stackTop: StateFlow<Float> = placeholderRepository.stackTop.asStateFlow() - - /** The y-coordinate in px of bottom of the contents of the notification stack. */ - val stackBottom: StateFlow<Float> = placeholderRepository.stackBottom.asStateFlow() + /** The alpha of the Notification Stack for the brightness mirror */ + val alphaForBrightnessMirror: StateFlow<Float> = + placeholderRepository.alphaForBrightnessMirror.asStateFlow() /** The height of the keyguard's available space bounds */ val constrainedAvailableSpace: StateFlow<Int> = @@ -101,32 +92,29 @@ constructor( */ val syntheticScroll: Flow<Float> = viewHeightRepository.syntheticScroll.asStateFlow() + /** + * Whether the current touch gesture is overscroll. If true, it means the NSSL has already + * consumed part of the gesture. + */ + val isCurrentGestureOverscroll: Flow<Boolean> = + viewHeightRepository.isCurrentGestureOverscroll.asStateFlow() + + /** Sets the alpha to apply to the NSSL for the brightness mirror */ + fun setAlphaForBrightnessMirror(alpha: Float) { + placeholderRepository.alphaForBrightnessMirror.value = alpha + } + /** Sets the position of the notification stack in the current scene. */ fun setShadeScrimBounds(bounds: ShadeScrimBounds?) { check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } 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 } - /** Sets the y-coord in px of the top of the contents of the notification stack. */ - fun setStackTop(stackTop: Float) { - placeholderRepository.stackTop.value = stackTop - } - - /** Sets the y-coord in px of the bottom of the contents of the notification stack. */ - fun setStackBottom(stackBottom: Float) { - placeholderRepository.stackBottom.value = stackBottom - } - /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { placeholderRepository.scrolledToTop.value = scrolledToTop @@ -137,6 +125,11 @@ constructor( viewHeightRepository.syntheticScroll.value = delta } + /** Sets whether the current touch gesture is overscroll. */ + fun setCurrentGestureOverscroll(isOverscroll: Boolean) { + viewHeightRepository.isCurrentGestureOverscroll.value = isOverscroll + } + fun setConstrainedAvailableSpace(height: Int) { placeholderRepository.constrainedAvailableSpace.value = height } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt index 20e8cac6e94b..9b21fa9bbe35 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt @@ -29,11 +29,10 @@ import com.android.systemui.statusbar.policy.SplitShadeStateController import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -54,9 +53,9 @@ constructor( private val _topPosition = MutableStateFlow(0f) val topPosition = _topPosition.asStateFlow() - private val _notificationStackChanged = MutableSharedFlow<Unit>(extraBufferCapacity = 1) + private val _notificationStackChanged = MutableStateFlow(0L) /** An internal modification was made to notifications */ - val notificationStackChanged = _notificationStackChanged.asSharedFlow() + val notificationStackChanged = _notificationStackChanged.debounce(20L) val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> = configurationRepository.onAnyConfigurationChange @@ -113,7 +112,7 @@ constructor( /** An internal modification was made to notifications */ fun notificationStackChanged() { - _notificationStackChanged.tryEmit(Unit) + _notificationStackChanged.value = _notificationStackChanged.value + 1 } data class ConfigurationBasedDimensions( 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 f356578ea561..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,12 +25,22 @@ 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. */ fun asView(): View + /** Max alpha for this view */ + fun setMaxAlpha(alpha: Float) + /** Set the clipping bounds used when drawing */ fun setScrimClippingShape(shape: ShadeScrimShape?) @@ -48,8 +58,8 @@ interface NotificationScrollView { /** Set a consumer for synthetic scroll events */ fun setSyntheticScrollConsumer(consumer: Consumer<Float>?) - /** Set a consumer for stack height changed events */ - fun setStackHeightConsumer(consumer: Consumer<Float>?) + /** Set a consumer for current gesture overscroll events */ + fun setCurrentGestureOverscrollConsumer(consumer: Consumer<Boolean>?) /** Set a consumer for heads up height changed events */ fun setHeadsUpHeightConsumer(consumer: Consumer<Float>?) @@ -61,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 6909907930e7..622d8e7b2307 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 @@ -78,8 +78,7 @@ constructor( .collect { view.setScrimClippingShape(it) } } - launch { viewModel.stackTop.collect { view.setStackTop(it) } } - launch { viewModel.stackBottom.collect { view.setStackBottom(it) } } + launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } } launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } } @@ -88,11 +87,11 @@ constructor( launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) - view.setStackHeightConsumer(viewModel.stackHeightConsumer) + view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) - view.setStackHeightConsumer(null) + view.setCurrentGestureOverscrollConsumer(null) view.setHeadsUpHeightConsumer(null) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt index 3a89630ebe77..b54f9c4c6d32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.domain.interactor.RemoteInputInteractor import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor @@ -59,7 +58,6 @@ constructor( activeNotificationsInteractor: ActiveNotificationsInteractor, notificationStackInteractor: NotificationStackInteractor, private val headsUpNotificationInteractor: HeadsUpNotificationInteractor, - keyguardInteractor: KeyguardInteractor, remoteInputInteractor: RemoteInputInteractor, seenNotificationsInteractor: SeenNotificationsInteractor, shadeInteractor: ShadeInteractor, @@ -277,11 +275,12 @@ constructor( if (NotificationsHeadsUpRefactor.isUnexpectedlyInLegacyMode()) { flowOf(false) } else { - combine(keyguardInteractor.isKeyguardShowing, shadeInteractor.isShadeFullyExpanded) { - (isKeyguardShowing, isShadeFullyExpanded) -> - // TODO(b/325936094) use isShadeFullyCollapsed instead - !isKeyguardShowing && !isShadeFullyExpanded - } + combine( + notificationStackInteractor.isShowingOnLockscreen, + shadeInteractor.isShadeFullyCollapsed + ) { (isKeyguardShowing, isShadeFullyCollapsed) -> + !isKeyguardShowing && isShadeFullyCollapsed + } .dumpWhileCollecting("headsUpAnimationsEnabled") } } 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 7aeff7119192..61373815db1c 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 @@ -32,12 +32,12 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.Notificati import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationTransitionThresholds.EXPANSION_FOR_MAX_SCRIM_ALPHA import com.android.systemui.util.kotlin.FlowDumperImpl import dagger.Lazy +import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import javax.inject.Inject /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */ @SysUISingleton @@ -65,7 +65,7 @@ constructor( ) { shadeExpansion, qsExpansion, transitionState -> when (transitionState) { is ObservableTransitionState.Idle -> { - if (transitionState.scene == Scenes.Lockscreen) { + if (transitionState.currentScene == Scenes.Lockscreen) { 1f } else { shadeExpansion @@ -122,10 +122,14 @@ constructor( } .dumpWhileCollecting("shadeScrimShape") - /** The y-coordinate in px of top of the contents of the notification stack. */ - val stackTop: Flow<Float> = stackAppearanceInteractor.stackTop.dumpValue("stackTop") - /** The y-coordinate in px of bottom of the contents of the notification stack. */ - val stackBottom: Flow<Float> = stackAppearanceInteractor.stackBottom.dumpValue("stackBottom") + /** + * Max alpha to apply directly to the view based on the compose placeholder. + * + * TODO(b/338590620): Migrate alphas from [SharedNotificationContainerViewModel] into this flow + */ + val maxAlpha: Flow<Float> = + stackAppearanceInteractor.alphaForBrightnessMirror.dumpValue("maxAlpha") + /** * Whether the notification stack is scrolled to the top; i.e., it cannot be scrolled down any * further. @@ -137,8 +141,12 @@ constructor( /** Receives the amount (px) that the stack should scroll due to internal expansion. */ val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll - /** Receives the height of the contents of the notification stack. */ - val stackHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setStackHeight + /** + * Receives whether the current touch gesture is overscroll as it has already been consumed by + * the stack. + */ + val currentGestureOverscrollConsumer: (Boolean) -> Unit = + stackAppearanceInteractor::setCurrentGestureOverscroll /** 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 bf3b2c94ab7e..97b86e3371f5 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 @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel -import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlagsClassic @@ -57,18 +56,6 @@ constructor( interactor.setShadeScrimBounds(bounds) } - /** Notifies that the bounds of the notification placeholder have changed. */ - fun onStackBoundsChanged( - top: Float, - bottom: Float, - ) { - keyguardInteractor.setNotificationContainerBounds( - NotificationContainerBounds(top = top, bottom = bottom) - ) - interactor.setStackTop(top) - interactor.setStackBottom(bottom) - } - /** Sets the available space */ fun onConstrainedAvailableSpaceChanged(height: Int) { interactor.setConstrainedAvailableSpace(height) @@ -78,17 +65,15 @@ constructor( interactor.setHeadsUpTop(headsUpTop) } + /** Sets the content alpha for the current state of the brightness mirror */ + fun setAlphaForBrightnessMirror(alpha: Float) { + interactor.setAlphaForBrightnessMirror(alpha) + } + /** Corner rounding of the stack */ 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") @@ -106,6 +91,13 @@ constructor( val syntheticScroll: Flow<Float> = interactor.syntheticScroll.dumpWhileCollecting("syntheticScroll") + /** + * Whether the current touch gesture is overscroll. If true, it means the NSSL has already + * consumed part of the gesture. + */ + val isCurrentGestureOverscroll: Flow<Boolean> = + interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll") + /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { interactor.setScrolledToTop(scrolledToTop) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index ac4bd09b1687..3393321f887b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -26,6 +26,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD @@ -64,12 +65,13 @@ import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransition import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToLockscreenTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor -import com.android.systemui.util.kotlin.BooleanFlowOperators.and -import com.android.systemui.util.kotlin.BooleanFlowOperators.or +import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf +import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.kotlin.FlowDumperImpl import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine import javax.inject.Inject @@ -246,7 +248,7 @@ constructor( keyguardTransitionInteractor.finishedKeyguardState.map { state -> state == GLANCEABLE_HUB }, - or( + anyOf( keyguardTransitionInteractor.isInTransitionToState(GLANCEABLE_HUB), keyguardTransitionInteractor.isInTransitionFromState(GLANCEABLE_HUB), ), @@ -295,8 +297,7 @@ constructor( return combine( isOnLockscreenWithoutShade, keyguardTransitionInteractor.isInTransition( - from = LOCKSCREEN, - to = AOD, + edge = Edge.create(from = LOCKSCREEN, to = AOD) ), ::Pair ) @@ -424,14 +425,14 @@ constructor( while (currentCoroutineContext().isActive) { emit(false) // Ensure states are inactive to start - and( + allOf( *toFlowArray(statesForHiddenKeyguard) { state -> keyguardTransitionInteractor.transitionValue(state).map { it == 0f } } ) .first { it } // Wait for a qualifying transition to begin - or( + anyOf( *toFlowArray(statesForHiddenKeyguard) { state -> keyguardTransitionInteractor .transitionStepsToState(state) @@ -446,7 +447,7 @@ constructor( // it is considered safe to reset alpha to 1f for HUNs. combine( keyguardInteractor.statusBarState, - and( + allOf( *toFlowArray(statesForHiddenKeyguard) { state -> keyguardTransitionInteractor.transitionValue(state).map { it == 0f @@ -636,7 +637,7 @@ constructor( showUnlimitedNotifications, shadeInteractor.isUserInteracting, availableHeight, - interactor.notificationStackChanged.onStart { emit(Unit) }, + interactor.notificationStackChanged, interactor.useExtraShelfSpace, ) { flows -> val showLimitedNotifications = flows[0] as Boolean diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 79e6a0aa9c8c..f83aed8db4b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -52,6 +52,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.log.SessionTracker; @@ -334,7 +335,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp @VisibleForTesting protected void consumeTransitionStepOnStartedKeyguardState(TransitionStep transitionStep) { if (transitionStep.getFrom() == KeyguardState.GONE) { - mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE); + mBiometricUnlockInteractor.setBiometricUnlockState(MODE_NONE, null); } } @@ -409,7 +410,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp Trace.endSection(); return; } - startWakeAndUnlock(MODE_SHOW_BOUNCER); + startWakeAndUnlock( + MODE_SHOW_BOUNCER, + BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType) + ); } @Override @@ -456,10 +460,19 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); } - startWakeAndUnlock(mode); + startWakeAndUnlock( + mode, + BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType) + ); } - public void startWakeAndUnlock(@WakeAndUnlockMode int mode) { + /** + * Wake and unlock the device in response to successful authentication using biometrics. + */ + public void startWakeAndUnlock( + @WakeAndUnlockMode int mode, + BiometricUnlockSource biometricUnlockSource + ) { mLogger.logStartWakeAndUnlock(mode); boolean wasDeviceInteractive = mUpdateMonitor.isDeviceInteractive(); mMode = mode; @@ -532,15 +545,18 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp case MODE_NONE: break; } - onModeChanged(mMode); + onModeChanged(mMode, biometricUnlockSource); Trace.endSection(); } - private void onModeChanged(@WakeAndUnlockMode int mode) { + private void onModeChanged( + @WakeAndUnlockMode int mode, + BiometricUnlockSource biometricUnlockSource + ) { for (BiometricUnlockEventsListener listener : mBiometricUnlockEventsListeners) { listener.onModeChanged(mode); } - mBiometricUnlockInteractor.setBiometricUnlockState(mode); + mBiometricUnlockInteractor.setBiometricUnlockState(mode, biometricUnlockSource); } private void onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType biometricSourceType) { @@ -719,7 +735,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp final boolean screenOff = !mUpdateMonitor.isDeviceInteractive(); if (!mVibratorHelper.hasVibrator() && screenOff) { mLogger.d("wakeup device on authentication failure (device doesn't have a vibrator)"); - startWakeAndUnlock(MODE_ONLY_WAKE); + startWakeAndUnlock( + MODE_ONLY_WAKE, + BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType) + ); } else if (biometricSourceType == BiometricSourceType.FINGERPRINT && mUpdateMonitor.isUdfpsSupported()) { long currUptimeMillis = mSystemClock.uptimeMillis(); @@ -732,7 +751,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (mNumConsecutiveFpFailures >= UDFPS_ATTEMPTS_BEFORE_SHOW_BOUNCER) { mLogger.logUdfpsAttemptThresholdMet(mNumConsecutiveFpFailures); - startWakeAndUnlock(MODE_SHOW_BOUNCER); + startWakeAndUnlock( + MODE_SHOW_BOUNCER, + BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType) + ); UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId()); mNumConsecutiveFpFailures = 0; } @@ -755,7 +777,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp || msgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT); if (fingerprintLockout) { mLogger.d("fingerprint locked out"); - startWakeAndUnlock(MODE_SHOW_BOUNCER); + startWakeAndUnlock( + MODE_SHOW_BOUNCER, + BiometricUnlockSource.Companion.fromBiometricSourceType(biometricSourceType) + ); UI_EVENT_LOGGER.log(BiometricUiEvent.BIOMETRIC_BOUNCER_SHOWN, getSessionId()); } @@ -897,7 +922,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp public interface BiometricUnlockEventsListener { /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */ default void onResetMode() {} - /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */ + /** Called when {@code mMode} has changed in + * {@link #startWakeAndUnlock(int, BiometricUnlockSource)}. */ default void onModeChanged(@WakeAndUnlockMode int mode) {} /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 7d9742849a15..8fb552f167bc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -283,12 +283,11 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner, CoreStartable void awakenDreams(); /** - * Handle a touch event while dreaming or on the glanceable hub when the touch was initiated - * within a prescribed swipeable area. This method is provided for cases where swiping in - * certain areas should be handled by CentralSurfaces instead (e.g. swiping hub open, opening - * the notification shade over dream or hub). + * Handle a touch event while dreaming when the touch was initiated within a prescribed + * swipeable area. This method is provided for cases where swiping in certain areas of a dream + * should be handled by CentralSurfaces instead (e.g. swiping communal hub open). */ - void handleExternalShadeWindowTouch(MotionEvent event); + void handleDreamTouch(MotionEvent event); boolean isBouncerShowing(); 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/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt index d5e66ff660c6..8af7ee8389e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -79,7 +79,7 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun updateScrimController() {} override fun shouldIgnoreTouch() = false override fun isDeviceInteractive() = false - override fun handleExternalShadeWindowTouch(event: MotionEvent?) {} + override fun handleDreamTouch(event: MotionEvent?) {} override fun awakenDreams() {} override fun isBouncerShowing() = false override fun isBouncerShowingScrimmed() = false 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 7630d43c3c7e..d3d2b1ebcb88 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; @@ -26,10 +27,12 @@ import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_ import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; +import static com.android.systemui.Flags.keyboardShortcutHelperRewrite; import static com.android.systemui.Flags.lightRevealMigration; import static com.android.systemui.Flags.newAodTransition; import static com.android.systemui.Flags.truncatedStatusBarIconsFix; import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; +import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; import static com.android.systemui.statusbar.StatusBarState.SHADE; @@ -289,7 +292,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks; private float mTransitionToFullShadeProgress = 0f; private final NotificationListContainer mNotifListContainer; - private final boolean mIsShortcutListSearchEnabled; private final KeyguardStateController.Callback mKeyguardStateControllerCallback = new KeyguardStateController.Callback() { @@ -789,7 +791,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mStatusBarSignalPolicy = statusBarSignalPolicy; mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager; mFeatureFlags = featureFlags; - mIsShortcutListSearchEnabled = featureFlags.isEnabled(Flags.SHORTCUT_LIST_SEARCH_LAYOUT); mKeyguardUnlockAnimationController = keyguardUnlockAnimationController; mMainExecutor = delayableExecutor; mMessageRouter = messageRouter; @@ -820,10 +821,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // TODO(b/190746471): Find a better home for this. DateTimeView.setReceiverHandler(timeTickHandler); - mMessageRouter.subscribeTo(KeyboardShortcutsMessage.class, - data -> toggleKeyboardShortcuts(data.mDeviceId)); - mMessageRouter.subscribeTo(MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU, - id -> dismissKeyboardShortcuts()); + if (!keyboardShortcutHelperRewrite()) { + mMessageRouter.subscribeTo( + KeyboardShortcutsMessage.class, + data -> toggleKeyboardShortcuts(data.mDeviceId)); + mMessageRouter.subscribeTo( + MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU, id -> dismissKeyboardShortcuts()); + } mMessageRouter.subscribeTo(AnimateExpandSettingsPanelMessage.class, data -> mCommandQueueCallbacks.animateExpandSettingsPanel(data.mSubpanel)); mMessageRouter.subscribeTo(MSG_LAUNCH_TRANSITION_TIMEOUT, @@ -1003,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(); } @@ -1872,10 +1870,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { String action = intent.getAction(); String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { - if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) { - KeyboardShortcutListSearch.dismiss(); - } else { - KeyboardShortcuts.dismiss(); + if (!keyboardShortcutHelperRewrite()) { + if (shouldUseTabletKeyboardShortcuts()) { + KeyboardShortcutListSearch.dismiss(); + } else { + KeyboardShortcuts.dismiss(); + } } mRemoteInputManager.closeRemoteInputs(); if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) { @@ -2179,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); @@ -2345,6 +2347,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } else if (mState == StatusBarState.KEYGUARD && !mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing() && mStatusBarKeyguardViewManager.isSecure()) { + Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer"); mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */); } } @@ -2802,7 +2805,16 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.setExpansionAffectsAlpha(!unlocking); if (mAlternateBouncerInteractor.isVisibleState()) { - if (!DeviceEntryUdfpsRefactor.isEnabled()) { + if (DeviceEntryUdfpsRefactor.isEnabled()) { + if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) + && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED + || mTransitionToFullShadeProgress > 0f)) { + // Assume scrim state for shade is already correct and do nothing + } else { + // Safeguard which prevents the scrim from being stuck in the wrong state + mScrimController.transitionTo(ScrimState.KEYGUARD); + } + } else { if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED || mTransitionToFullShadeProgress > 0f)) { @@ -2811,7 +2823,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); } } - // This will cancel the keyguardFadingAway animation if it is running. We need to do // this as otherwise it can remain pending and leave keyguard in a weird state. mUnlockScrimCallback.onCancelled(); @@ -2929,8 +2940,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { }; @Override - public void handleExternalShadeWindowTouch(MotionEvent event) { - getNotificationShadeWindowViewController().handleExternalTouch(event); + public void handleDreamTouch(MotionEvent event) { + getNotificationShadeWindowViewController().handleDreamTouch(event); } @Override @@ -2945,7 +2956,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } protected void toggleKeyboardShortcuts(int deviceId) { - if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) { + if (shouldUseTabletKeyboardShortcuts()) { KeyboardShortcutListSearch.toggle(mContext, deviceId); } else { KeyboardShortcuts.toggle(mContext, deviceId); @@ -2953,13 +2964,18 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } protected void dismissKeyboardShortcuts() { - if (mIsShortcutListSearchEnabled && Utilities.isLargeScreen(mContext)) { + if (shouldUseTabletKeyboardShortcuts()) { KeyboardShortcutListSearch.dismiss(); } else { KeyboardShortcuts.dismiss(); } } + private boolean shouldUseTabletKeyboardShortcuts() { + return mFeatureFlags.isEnabled(SHORTCUT_LIST_SEARCH_LAYOUT) + && Utilities.isLargeScreen(mContext); + } + private void clearNotificationEffects() { try { mBarService.clearNotificationEffects(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt index dea94162ad0e..2e1ab383538f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt @@ -59,7 +59,10 @@ class ConfigurationControllerImpl @Inject constructor( } override fun notifyThemeChanged() { - val listeners = ArrayList(listeners) + // Avoid concurrent modification exception + val listeners = synchronized(this.listeners) { + ArrayList(this.listeners) + } listeners.filterForEach({ this.listeners.contains(it) }) { it.onThemeChanged() @@ -68,8 +71,9 @@ class ConfigurationControllerImpl @Inject constructor( override fun onConfigurationChanged(newConfig: Configuration) { // Avoid concurrent modification exception - val listeners = ArrayList(listeners) - + val listeners = synchronized(this.listeners) { + ArrayList(this.listeners) + } listeners.filterForEach({ this.listeners.contains(it) }) { it.onConfigChanged(newConfig) } @@ -148,12 +152,16 @@ class ConfigurationControllerImpl @Inject constructor( } override fun addCallback(listener: ConfigurationListener) { - listeners.add(listener) + synchronized(listeners) { + listeners.add(listener) + } listener.onDensityOrFontScaleChanged() } override fun removeCallback(listener: ConfigurationListener) { - listeners.remove(listener) + synchronized(listeners) { + listeners.remove(listener) + } } override fun isLayoutRtl(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java index 5deb08a75dff..cff46ab812bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger; import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView; import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel; +import com.android.systemui.statusbar.pipeline.shared.ui.view.ModernStatusBarView; import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView; import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel; @@ -277,6 +278,15 @@ public class DemoStatusIcons extends StatusIconContainer implements DemoMode, Da addView(view, viewIndex, createLayoutParams()); } + /** Adds a bindable icon to the demo mode view. */ + public void addBindableIcon(StatusBarIconHolder.BindableIconHolder holder) { + // This doesn't do any correct ordering, and also doesn't check if we already have an + // existing icon for the slot. But since we hope to remove this class soon, we won't spend + // the time adding that logic. + ModernStatusBarView view = holder.getInitializer().createAndBind(mContext); + addView(view, createLayoutParams()); + } + public void onRemoveIcon(StatusIconDisplayable view) { if (view.getSlot().equals("wifi")) { if (view instanceof ModernStatusBarWifiView) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 4c3c7d56df50..11feb971db6d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -354,6 +354,7 @@ public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBar * since the headsUp manager might not have notified us yet of the state change. * * @return if the heads up status bar view should be shown + * @deprecated use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead. */ public boolean shouldBeVisible() { boolean notificationsShown = !mWakeUpCoordinator.getNotificationsFullyHidden(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index c4e0f31c9c74..16e9c717935c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -219,6 +219,7 @@ public class KeyguardIndicationTextView extends TextView { } private void setNextIndication() { + boolean forceAssertiveAccessibilityLiveRegion = false; if (mKeyguardIndicationInfo != null) { // First, update the style. // If a background is set on the text, we don't want shadow on the text @@ -239,8 +240,16 @@ public class KeyguardIndicationTextView extends TextView { } } setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null); + forceAssertiveAccessibilityLiveRegion = + mKeyguardIndicationInfo.getForceAssertiveAccessibilityLiveRegion(); + } + if (!forceAssertiveAccessibilityLiveRegion) { + setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_NONE); } setText(mMessage); + if (forceAssertiveAccessibilityLiveRegion) { + setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_ASSERTIVE); + } if (mAlwaysAnnounceText) { announceForAccessibility(mMessage); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 3cdf68f5c231..84e601848b91 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -41,6 +41,7 @@ import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; @@ -291,7 +292,16 @@ public class KeyguardStatusBarView extends RelativeLayout { } private boolean updateLayoutConsideringCutout(StatusBarContentInsetsProvider insetsProvider) { - mDisplayCutout = getRootWindowInsets().getDisplayCutout(); + return setDisplayCutout( + getRootWindowInsets().getDisplayCutout(), + insetsProvider); + } + + /** Sets the {@link DisplayCutout}, updating the view to render around the cutout. */ + public boolean setDisplayCutout( + @Nullable DisplayCutout displayCutout, + StatusBarContentInsetsProvider insetsProvider) { + mDisplayCutout = displayCutout; updateKeyguardStatusBarHeight(); updatePadding(insetsProvider); if (mDisplayCutout == null || insetsProvider.currentRotationHasCornerCutout()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 45d86c0e765c..f767262dbd4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -30,9 +30,11 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.util.MathUtils; +import android.view.DisplayCutout; import android.view.View; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.core.animation.Animator; import androidx.core.animation.AnimatorListenerAdapter; @@ -52,7 +54,6 @@ import com.android.systemui.res.R; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.DisableStateTracker; @@ -131,9 +132,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private View mSystemIconsContainer; private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory; - // TODO(b/273443374): remove - private NotificationMediaManager mNotificationMediaManager; - private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @Override @@ -300,7 +298,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat @Main Executor mainExecutor, @Background Executor backgroundExecutor, KeyguardLogger logger, - NotificationMediaManager notificationMediaManager, StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory ) { super(view); @@ -355,7 +352,6 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /* mask2= */ DISABLE2_SYSTEM_ICONS, this::updateViewState ); - mNotificationMediaManager = notificationMediaManager; mStatusOverlayHoverListenerFactory = statusOverlayHoverListenerFactory; } @@ -585,6 +581,17 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat } /** + * Passes the given {@link DisplayCutout} to the view. + * + * <p>This isn't needed when the view is part of a real view hierarchy. Only call this when the + * view is added to a Compose hierarchy where it doesn't actually receive any callback to its + * {@code OnApplyWindowInsetsListener}s. + */ + public void setDisplayCutout(@Nullable DisplayCutout displayCutout) { + mView.setDisplayCutout(displayCutout, mInsetsProvider); + } + + /** * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area) * during swiping up. */ @@ -653,10 +660,14 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat * whether heads up is visible. */ public void updateForHeadsUp() { + if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) { + // [KeyguardStatusBarViewBinder] handles visibility changes due to heads up states. + return; + } updateForHeadsUp(true); } - // TODO(b/328579846) bind the StatusBar visibility to heads up events + @VisibleForTesting void updateForHeadsUp(boolean animate) { boolean showingKeyguardHeadsUp = isKeyguardShowing() && mShadeViewStateProvider.shouldHeadsUpBeVisible(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt index 6b685118a462..bcc7db162ddd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt @@ -417,10 +417,10 @@ constructor( ) { // Reuse the biometric wake-and-unlock transition if we dismiss keyguard from a // pulse. - // TODO: Factor this transition out of BiometricUnlockController. + // TODO (b/338578036): Factor this transition out of BiometricUnlockController. biometricUnlockControllerLazy .get() - .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) + .startWakeAndUnlock(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, null) } if (keyguardStateController.isShowing) { statusBarKeyguardViewManagerLazy diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 0a88d6358d8e..fe001b35e958 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -64,6 +64,7 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.ScrimAlpha; import com.android.systemui.keyguard.shared.model.TransitionState; @@ -71,6 +72,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.shade.transition.LargeScreenShadeInterpolator; @@ -454,23 +456,32 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump }; // PRIMARY_BOUNCER->GONE - collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(PRIMARY_BOUNCER, GONE), + collectFlow(behindScrim, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(PRIMARY_BOUNCER, GONE)), mBouncerToGoneTransition, mMainDispatcher); collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(), mScrimAlphaConsumer, mMainDispatcher); // ALTERNATE_BOUNCER->GONE - collectFlow(behindScrim, mKeyguardTransitionInteractor.transition(ALTERNATE_BOUNCER, GONE), + collectFlow(behindScrim, mKeyguardTransitionInteractor.transition( + Edge.Companion.create(ALTERNATE_BOUNCER, Scenes.Gone), + Edge.Companion.create(ALTERNATE_BOUNCER, GONE)), mBouncerToGoneTransition, mMainDispatcher); collectFlow(behindScrim, mAlternateBouncerToGoneTransitionViewModel.getScrimAlpha(), mScrimAlphaConsumer, mMainDispatcher); // LOCKSCREEN<->GLANCEABLE_HUB + collectFlow( + behindScrim, + mKeyguardTransitionInteractor.transition( + Edge.Companion.create(LOCKSCREEN, Scenes.Communal), + Edge.Companion.create(LOCKSCREEN, GLANCEABLE_HUB)), + mGlanceableHubConsumer, + mMainDispatcher); collectFlow(behindScrim, - mKeyguardTransitionInteractor.transition(LOCKSCREEN, GLANCEABLE_HUB), - mGlanceableHubConsumer, mMainDispatcher); - collectFlow(behindScrim, - mKeyguardTransitionInteractor.transition(GLANCEABLE_HUB, LOCKSCREEN), + mKeyguardTransitionInteractor.transition( + Edge.Companion.create(Scenes.Communal, LOCKSCREEN), + Edge.Companion.create(GLANCEABLE_HUB, LOCKSCREEN)), mGlanceableHubConsumer, mMainDispatcher); } @@ -1644,6 +1655,10 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScreenOn = false; } + public boolean isScreenOn() { + return mScreenOn; + } + public void setExpansionAffectsAlpha(boolean expansionAffectsAlpha) { mExpansionAffectsAlpha = expansionAffectsAlpha; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt index bef0b286ebf8..08a890dbadb5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.kt @@ -169,16 +169,19 @@ open class StatusBarIconHolder private constructor() { * StatusBarIconController will register all available bindable icons on init (see * [BindableIconsRepository]), and will ignore any call to setIcon for these. * - * [initializer] a view creator that can bind the relevant view models to the created view. + * @property initializer a view creator that can bind the relevant view models to the created + * view. + * @property slot the name of the slot that this holder is used for. */ - class BindableIconHolder(val initializer: ModernStatusBarViewCreator) : StatusBarIconHolder() { + class BindableIconHolder(val initializer: ModernStatusBarViewCreator, val slot: String) : + StatusBarIconHolder() { override var type: Int = TYPE_BINDABLE /** This is unused, as bindable icons use their own view binders to control visibility */ override var isVisible: Boolean = true override fun toString(): String { - return ("StatusBarIconHolder(type=BINDABLE)") + return ("StatusBarIconHolder(type=BINDABLE, slot=$slot)") } } } 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 7301b87d815b..5bbc3bd92543 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -655,13 +655,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb * device state and touch handling. The bouncer MUST have been notified that it is about to * show if any subsequent events are to be handled. */ - if (beginShowingBouncer(event)) { - if (SceneContainerFlag.isEnabled()) { - mSceneInteractorLazy.get().changeScene( - Scenes.Bouncer, "StatusBarKeyguardViewManager.onPanelExpansionChanged"); - } else { - mPrimaryBouncerInteractor.show(/* isScrimmed= */false); - } + if (!SceneContainerFlag.isEnabled() && beginShowingBouncer(event)) { + mPrimaryBouncerInteractor.show(/* isScrimmed= */false); } if (!primaryBouncerIsOrWillBeShowing()) { @@ -695,10 +690,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void show(Bundle options) { Trace.beginSection("StatusBarKeyguardViewManager#show"); mNotificationShadeWindowController.setKeyguardShowing(true); - if (SceneContainerFlag.isEnabled()) { - mSceneInteractorLazy.get().changeScene( - Scenes.Lockscreen, "StatusBarKeyguardViewManager.show"); - } mKeyguardStateController.notifyKeyguardState(true, mKeyguardStateController.isOccluded()); reset(true /* hideBouncerWhenShowing */); SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED, @@ -744,6 +735,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void showBouncer(boolean scrimmed) { if (DeviceEntryUdfpsRefactor.isEnabled()) { if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) { + Log.d(TAG, "showBouncer:alternateBouncer.forceShow()"); mAlternateBouncerInteractor.forceShow(); updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); } else { @@ -869,6 +861,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } if (DeviceEntryUdfpsRefactor.isEnabled()) { + Log.d(TAG, "dismissWithAction:alternateBouncer.forceShow()"); mAlternateBouncerInteractor.forceShow(); updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); } else { @@ -1546,7 +1539,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } if (KeyguardWmStateRefactor.isEnabled()) { - mKeyguardTransitionInteractor.startDismissKeyguardTransition(); + mKeyguardTransitionInteractor.startDismissKeyguardTransition( + "SBKVM#keyguardAuthenticated"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index e1a7f22a913a..e92058bf034a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -96,6 +96,20 @@ import javax.inject.Inject; @SysUISingleton public class StatusBarNotificationActivityStarter implements NotificationActivityStarter { + /** + * Helps to avoid recalculation of values provided to + * {@link #onDismiss(PendingIntent, boolean, boolean, boolean)}} method + */ + private interface OnKeyguardDismissedAction { + /** + * Invoked when keyguard is dismissed + * + * @return is used as return value for {@link ActivityStarter.OnDismissAction#onDismiss()} + */ + boolean onDismiss(PendingIntent intent, boolean isActivityIntent, boolean animate, + boolean showOverTheLockScreen); + } + private final Context mContext; private final int mDisplayId; @@ -207,6 +221,30 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } /** + * Called when the user clicks on the notification bubble icon. + * + * @param entry notification that bubble icon was clicked + */ + @Override + public void onNotificationBubbleIconClicked(NotificationEntry entry) { + Runnable action = () -> { + mBubblesManagerOptional.ifPresent(bubblesManager -> + bubblesManager.onUserChangedBubble(entry, !entry.isBubble())); + mHeadsUpManager.removeNotification(entry.getKey(), /* releaseImmediately= */ true); + }; + if (entry.isBubble()) { + // entry is being un-bubbled, no need to unlock + action.run(); + } else { + performActionAfterKeyguardDismissed(entry, + (intent, isActivityIntent, animate, showOverTheLockScreen) -> { + action.run(); + return false; + }); + } + } + + /** * Called when a notification is clicked. * * @param entry notification that was clicked @@ -217,7 +255,15 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit mLogger.logStartingActivityFromClick(entry, row.isHeadsUpState(), mKeyguardStateController.isVisible(), mNotificationShadeWindowController.getPanelExpanded()); + OnKeyguardDismissedAction action = + (intent, isActivityIntent, animate, showOverTheLockScreen) -> + performActionOnKeyguardDismissed(entry, row, intent, isActivityIntent, + animate, showOverTheLockScreen); + performActionAfterKeyguardDismissed(entry, action); + } + private void performActionAfterKeyguardDismissed(NotificationEntry entry, + OnKeyguardDismissedAction action) { if (mRemoteInputManager.isRemoteInputActive(entry)) { // We have an active remote input typed and the user clicked on the notification. // this was probably unintentional, so we're closing the edit text instead. @@ -251,8 +297,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit ActivityStarter.OnDismissAction postKeyguardAction = new ActivityStarter.OnDismissAction() { @Override public boolean onDismiss() { - return handleNotificationClickAfterKeyguardDismissed( - entry, row, intent, isActivityIntent, animate, showOverLockscreen); + return action.onDismiss(intent, isActivityIntent, animate, showOverLockscreen); } @Override @@ -271,7 +316,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit } } - private boolean handleNotificationClickAfterKeyguardDismissed( + private boolean performActionOnKeyguardDismissed( NotificationEntry entry, ExpandableNotificationRow row, PendingIntent intent, @@ -282,7 +327,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit final Runnable runnable = () -> handleNotificationClickAfterPanelCollapsed( entry, row, intent, isActivityIntent, animate); - if (showOverLockscreen) { mShadeController.addPostCollapseAction(runnable); mShadeController.collapseShade(true /* animate */); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java index a6284e3f62ab..4505a1d2c548 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java @@ -196,7 +196,22 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, // The group isn't expanded, let's make sure it's visible! mGroupExpansionManager.toggleGroupExpansion(row.getEntry()); } - row.setUserExpanded(true); + + if (android.app.Flags.compactHeadsUpNotificationReply() + && row.isCompactConversationHeadsUpOnScreen()) { + // Notification can be system expanded true and it is set user expanded in + // activateRemoteInput. notifyHeightChanged also doesn't work as visibleType doesn't + // change. To expand huning notification properly, we need set userExpanded false. + if (!row.isPinned() && row.isExpanded()) { + row.setUserExpanded(false); + } + // expand notification emits expanded information to HUN listener. + row.expandNotification(); + } else { + // Note: Since Normal HUN has remote input view in it, we don't expect to hit + // onMakeExpandedVisibleForRemoteInput from activateRemoteInput for Normal HUN. + row.setUserExpanded(true); + } row.getPrivateLayout().setOnExpandedVisibleListener(runnable); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 4fc11df5aaa5..e5c86c8bc7e9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -119,7 +119,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private MultiSourceMinAlphaController mEndSideAlphaController; private LinearLayout mEndSideContent; private View mClockView; - private View mOngoingCallChip; + private View mOngoingActivityChip; private View mNotificationIconAreaInner; // Visibilities come in from external system callers via disable flags, but we also sometimes // modify the visibilities internally. We need to store both so that we don't accidentally @@ -345,7 +345,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content); mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent); mClockView = mStatusBar.findViewById(R.id.clock); - mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip); + mOngoingActivityChip = mStatusBar.findViewById(R.id.ongoing_activity_chip); showEndSideContent(false); showClock(false); initOperatorName(); @@ -548,6 +548,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private StatusBarVisibilityModel calculateInternalModel( StatusBarVisibilityModel externalModel) { + // TODO(b/328393714) use HeadsUpNotificationInteractor.showHeadsUpStatusBar instead. boolean headsUpVisible = mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible(); @@ -594,9 +595,9 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue // so if the icons are disabled then the call chip should be, too.) boolean showOngoingCallChip = hasOngoingCall && !disableNotifications; if (showOngoingCallChip) { - showOngoingCallChip(animate); + showOngoingActivityChip(animate); } else { - hideOngoingCallChip(animate); + hideOngoingActivityChip(animate); } mOngoingCallController.notifyChipVisibilityChanged(showOngoingCallChip); } @@ -688,14 +689,19 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue animateShow(mClockView, animate); } - /** Hides the ongoing call chip. */ - public void hideOngoingCallChip(boolean animate) { - animateHiddenState(mOngoingCallChip, View.GONE, animate); + /** Hides the ongoing activity chip. */ + private void hideOngoingActivityChip(boolean animate) { + animateHiddenState(mOngoingActivityChip, View.GONE, animate); } - /** Displays the ongoing call chip. */ - public void showOngoingCallChip(boolean animate) { - animateShow(mOngoingCallChip, animate); + /** + * Displays the ongoing activity chip. + * + * For now, this chip will only ever contain the ongoing call information, but after b/332662551 + * feature is implemented, it will support different kinds of ongoing activities. + */ + private void showOngoingActivityChip(boolean animate) { + animateShow(mOngoingActivityChip, animate); } /** @@ -803,7 +809,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private void initOngoingCallChip() { mOngoingCallController.addCallback(mOngoingCallListener); - mOngoingCallController.setChipView(mOngoingCallChip); + mOngoingCallController.setChipView(mOngoingActivityChip); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt index ec88b6c56477..a7d4ce30a191 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt @@ -36,6 +36,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.chips.ui.view.ChipChronometer +import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -145,8 +147,8 @@ class OngoingCallController @Inject constructor( fun setChipView(chipView: View) { tearDownChipView() this.chipView = chipView - val backgroundView: OngoingCallBackgroundContainer? = - chipView.findViewById(R.id.ongoing_call_chip_background) + val backgroundView: ChipBackgroundContainer? = + chipView.findViewById(R.id.ongoing_activity_chip_background) backgroundView?.maxHeightFetcher = { statusBarWindowController.statusBarHeight } if (hasOngoingCall()) { updateChip() @@ -226,7 +228,7 @@ class OngoingCallController @Inject constructor( if (callNotificationInfo == null) { return } val currentChipView = chipView val backgroundView = - currentChipView?.findViewById<View>(R.id.ongoing_call_chip_background) + currentChipView?.findViewById<View>(R.id.ongoing_activity_chip_background) val intent = callNotificationInfo?.intent if (currentChipView != null && backgroundView != null && intent != null) { currentChipView.setOnClickListener { @@ -271,8 +273,8 @@ class OngoingCallController @Inject constructor( @VisibleForTesting fun tearDownChipView() = chipView?.getTimeView()?.stop() - private fun View.getTimeView(): OngoingCallChronometer? { - return this.findViewById(R.id.ongoing_call_chip_time) + private fun View.getTimeView(): ChipChronometer? { + return this.findViewById(R.id.ongoing_activity_chip_time) } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt index 9c78ab42a14a..886481e64dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/data/repository/OngoingCallRepository.kt @@ -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. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java index 0ed94203ffa0..5ad737684ca1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/IconManager.java @@ -37,6 +37,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable; import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider; import com.android.systemui.statusbar.phone.DemoStatusIcons; import com.android.systemui.statusbar.phone.StatusBarIconHolder; +import com.android.systemui.statusbar.phone.StatusBarIconHolder.BindableIconHolder; import com.android.systemui.statusbar.phone.StatusBarLocation; import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter; import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder; @@ -49,7 +50,9 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWi import com.android.systemui.util.Assert; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Turns info from StatusBarIconController into ImageViews in a ViewGroup. @@ -60,6 +63,11 @@ public class IconManager implements DemoModeCommandReceiver { private final LocationBasedWifiViewModel mWifiViewModel; private final MobileIconsViewModel mMobileIconsViewModel; + /** + * Stores the list of bindable icons that have been added, keyed on slot name. This ensures + * we don't accidentally add the same bindable icon twice. + */ + private final Map<String, BindableIconHolder> mBindableIcons = new HashMap<>(); protected final Context mContext; protected int mIconSize; // Whether or not these icons show up in dumpsys @@ -142,7 +150,7 @@ public class IconManager implements DemoModeCommandReceiver { case TYPE_MOBILE_NEW -> addNewMobileIcon(index, slot, holder.getTag()); case TYPE_BINDABLE -> // Safe cast, since only BindableIconHolders can set this tag on themselves - addBindableIcon((StatusBarIconHolder.BindableIconHolder) holder, index); + addBindableIcon((BindableIconHolder) holder, index); default -> null; }; } @@ -162,10 +170,14 @@ public class IconManager implements DemoModeCommandReceiver { * icon view, we can simply create the icon when requested and allow the * ViewBinder to control its visual state. */ - protected StatusIconDisplayable addBindableIcon(StatusBarIconHolder.BindableIconHolder holder, + protected StatusIconDisplayable addBindableIcon(BindableIconHolder holder, int index) { + mBindableIcons.put(holder.getSlot(), holder); ModernStatusBarView view = holder.getInitializer().createAndBind(mContext); mGroup.addView(view, index, onCreateLayoutParams()); + if (mIsInDemoMode) { + mDemoStatusIcons.addBindableIcon(holder); + } return view; } @@ -278,6 +290,9 @@ public class IconManager implements DemoModeCommandReceiver { if (mDemoStatusIcons == null) { mDemoStatusIcons = createDemoStatusIcons(); mDemoStatusIcons.addModernWifiView(mWifiViewModel); + for (BindableIconHolder holder : mBindableIcons.values()) { + mDemoStatusIcons.addBindableIcon(holder); + } } mDemoStatusIcons.onDemoModeStarted(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java index 92d90af6f921..fabf858d0832 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ui/StatusBarIconControllerImpl.java @@ -213,7 +213,8 @@ public class StatusBarIconControllerImpl implements Tunable, StatusBarIconHolder existingHolder = mStatusBarIconList.getIconHolder(icon.getSlot(), 0); // Expected to be null if (existingHolder == null) { - BindableIconHolder bindableIcon = new BindableIconHolder(icon.getInitializer()); + BindableIconHolder bindableIcon = + new BindableIconHolder(icon.getInitializer(), icon.getSlot()); setIcon(icon.getSlot(), bindableIcon); } else { Log.e(TAG, "addBindableIcon called, but icon has already been added. Ignoring"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index b80ff388955a..88ca9e5f1744 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -41,6 +41,8 @@ import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyIm import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxyImpl import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository +import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepositorySwitcher +import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModelImpl @@ -83,8 +85,13 @@ abstract class StatusBarPipelineModule { abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository @Binds - abstract fun deviceBasedSatelliteRepository( + abstract fun realDeviceBasedSatelliteRepository( impl: DeviceBasedSatelliteRepositoryImpl + ): RealDeviceBasedSatelliteRepository + + @Binds + abstract fun deviceBasedSatelliteRepository( + impl: DeviceBasedSatelliteRepositorySwitcher ): DeviceBasedSatelliteRepository @Binds @@ -222,7 +229,7 @@ abstract class StatusBarPipelineModule { @SysUISingleton @OemSatelliteInputLog fun provideOemSatelliteInputLog(factory: LogBufferFactory): LogBuffer { - return factory.create("DeviceBasedSatelliteInputLog", 32) + return factory.create("DeviceBasedSatelliteInputLog", 150) } const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index d4b2dbff078b..2e54972c4950 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -53,6 +53,27 @@ constructor( ) } + fun logTopLevelServiceStateBroadcastEmergencyOnly(subId: Int, serviceState: ServiceState) { + buffer.log( + TAG, + LogLevel.INFO, + { + int1 = subId + bool1 = serviceState.isEmergencyOnly + }, + { "ACTION_SERVICE_STATE for subId=$int1. ServiceState.isEmergencyOnly=$bool1" } + ) + } + + fun logTopLevelServiceStateBroadcastMissingExtras(subId: Int) { + buffer.log( + TAG, + LogLevel.INFO, + { int1 = subId }, + { "ACTION_SERVICE_STATE for subId=$int1. Intent is missing extras. Ignoring" } + ) + } + fun logOnSignalStrengthsChanged(signalStrength: SignalStrength, subId: Int) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.kt new file mode 100644 index 000000000000..cce3eb02023b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ServiceStateModel.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.statusbar.pipeline.mobile.data.model + +import android.telephony.ServiceState + +/** + * Simplified representation of a [ServiceState] for use in SystemUI. Add any fields that we need to + * extract from service state here for consumption downstream + */ +data class ServiceStateModel(val isEmergencyOnly: Boolean) { + companion object { + fun fromServiceState(serviceState: ServiceState): ServiceStateModel { + return ServiceStateModel(isEmergencyOnly = serviceState.isEmergencyOnly) + } + } +} 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/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt index 9471574fc755..5ad8bf1652b6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt @@ -21,6 +21,7 @@ import android.telephony.SubscriptionManager import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.MobileMappings.Config +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -92,6 +93,19 @@ interface MobileConnectionsRepository { val defaultMobileIconGroup: Flow<MobileIconGroup> /** + * [deviceServiceState] is equivalent to the last [Intent.ACTION_SERVICE_STATE] broadcast with a + * subscriptionId of -1 (aka [SubscriptionManager.INVALID_SUBSCRIPTION_ID]). + * + * While each [MobileConnectionsRepository] listens for the service state of each subscription, + * there is potentially a service state associated with the device itself. This value can be + * used to calculate e.g., the emergency calling capability of the device (as opposed to the + * emergency calling capability of an individual mobile connection) + * + * Note: this is a [StateFlow] using an eager sharing strategy. + */ + val deviceServiceState: StateFlow<ServiceStateModel?> + + /** * If any active SIM on the device is in * [android.telephony.TelephonyManager.SIM_STATE_PIN_REQUIRED] or * [android.telephony.TelephonyManager.SIM_STATE_PUK_REQUIRED] or diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt index 8a8e33efbcef..b0681525a137 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt @@ -25,6 +25,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl @@ -151,6 +152,15 @@ constructor( override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> = activeRepo.flatMapLatest { it.defaultMobileIconGroup } + override val deviceServiceState: StateFlow<ServiceStateModel?> = + activeRepo + .flatMapLatest { it.deviceServiceState } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + realRepository.deviceServiceState.value + ) + override val isAnySimSecure: Flow<Boolean> = activeRepo.flatMapLatest { it.isAnySimSecure } override fun getIsAnySimSecure(): Boolean = activeRepo.value.getIsAnySimSecure() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt index 2b3c6326032c..a944e9133a31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository @@ -136,6 +137,9 @@ constructor( override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G) + // TODO(b/339023069): demo command for device-based connectivity state + override val deviceServiceState: StateFlow<ServiceStateModel?> = MutableStateFlow(null) + override val isAnySimSecure: Flow<Boolean> = flowOf(getIsAnySimSecure()) override fun getIsAnySimSecure(): Boolean = false 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..c32f0e8090ac 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 @@ -18,8 +18,10 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.annotation.SuppressLint import android.content.Context +import android.content.Intent import android.content.IntentFilter import android.telephony.CarrierConfigManager +import android.telephony.ServiceState import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID @@ -35,7 +37,6 @@ import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.mobile.MobileMappings.Config import com.android.systemui.Dumpable import com.android.systemui.broadcast.BroadcastDispatcher -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -47,6 +48,7 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.Airplane import com.android.systemui.statusbar.pipeline.dagger.MobileSummaryLog import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy @@ -55,6 +57,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.util.kotlin.pairwise +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import java.io.PrintWriter import java.lang.ref.WeakReference import javax.inject.Inject @@ -68,6 +71,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest @@ -169,6 +173,35 @@ constructor( } .flowOn(bgDispatcher) + /** Note that this flow is eager, so we don't miss any state */ + override val deviceServiceState: StateFlow<ServiceStateModel?> = + broadcastDispatcher + .broadcastFlow(IntentFilter(Intent.ACTION_SERVICE_STATE)) { intent, _ -> + val subId = + intent.getIntExtra( + SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, + INVALID_SUBSCRIPTION_ID + ) + + val extras = intent.extras + if (extras == null) { + logger.logTopLevelServiceStateBroadcastMissingExtras(subId) + return@broadcastFlow null + } + + val serviceState = ServiceState.newFromBundle(extras) + logger.logTopLevelServiceStateBroadcastEmergencyOnly(subId, serviceState) + if (subId == INVALID_SUBSCRIPTION_ID) { + // Assume that -1 here is the device's service state. We don't care about + // other ones. + ServiceStateModel.fromServiceState(serviceState) + } else { + null + } + } + .filterNotNull() + .stateIn(scope, SharingStarted.Eagerly, null) + /** * State flow that emits the set of mobile data subscriptions, each represented by its own * [SubscriptionModel]. @@ -424,6 +457,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..cc4d5689c3fb 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 @@ -111,6 +111,13 @@ interface MobileIconsInteractor { val isForceHidden: Flow<Boolean> /** + * True if the device-level service state (with -1 subscription id) reports emergency calls + * only. This value is only useful when there are no other subscriptions OR all existing + * subscriptions report that they are not in service. + */ + val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> + + /** * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given * subId. */ @@ -172,21 +179,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 +223,8 @@ constructor( subscriptionsBasedFilteredSubs, mobileConnectionsRepo.activeMobileDataSubscriptionId, connectivityRepository.vcnSubId, - ) { unfilteredSubs, activeId, vcnSubId -> - filterSubsBasedOnOpportunistic( - unfilteredSubs, - activeId, - vcnSubId, - ) + ) { preFilteredSubs, activeId, vcnSubId -> + filterSubsBasedOnOpportunistic(preFilteredSubs, activeId, vcnSubId) } .distinctUntilChanged() .logDiffsForTable( @@ -369,6 +384,9 @@ constructor( .map { it.contains(ConnectivitySlot.MOBILE) } .stateIn(scope, SharingStarted.WhileSubscribed(), false) + override val isDeviceInEmergencyCallsOnlyMode: Flow<Boolean> = + mobileConnectionsRepo.deviceServiceState.map { it?.isEmergencyOnly ?: false } + /** Vends out new [MobileIconInteractor] for a particular subId */ override fun getMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor = reuseCache[subId]?.get() ?: createMobileConnectionInteractorForSubId(subId) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt index ad8b8100f14d..d38e834ff1e2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.satellite.data import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** * Device-based satellite refers to the capability of a device to connect directly to a satellite @@ -26,12 +26,22 @@ import kotlinx.coroutines.flow.Flow */ interface DeviceBasedSatelliteRepository { /** See [SatelliteConnectionState] for available states */ - val connectionState: Flow<SatelliteConnectionState> + val connectionState: StateFlow<SatelliteConnectionState> /** 0-4 level (similar to wifi and mobile) */ // @IntRange(from = 0, to = 4) - val signalStrength: Flow<Int> + val signalStrength: StateFlow<Int> /** Clients must observe this property, as device-based satellite is location-dependent */ - val isSatelliteAllowedForCurrentLocation: Flow<Boolean> + val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean> } + +/** + * A no-op interface used for Dagger bindings. + * + * [DeviceBasedSatelliteRepositorySwitcher] needs to inject both the real repository and the demo + * mode repository, both of which implement the [DeviceBasedSatelliteRepository] interface. To help + * distinguish the two for the switcher, [DeviceBasedSatelliteRepositoryImpl] will implement this + * [RealDeviceBasedSatelliteRepository] interface. + */ +interface RealDeviceBasedSatelliteRepository : DeviceBasedSatelliteRepository diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt new file mode 100644 index 000000000000..6b1bc65e86db --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.data + +import android.os.Bundle +import androidx.annotation.VisibleForTesting +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.demomode.DemoMode +import com.android.systemui.demomode.DemoModeController +import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteRepository +import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import javax.inject.Inject +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.flatMapLatest +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn + +/** + * A provider for the [DeviceBasedSatelliteRepository] interface that can choose between the Demo + * and Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo], + * which switches based on the latest information from [DemoModeController], and switches every flow + * in the interface to point to the currently-active provider. This allows us to put the demo mode + * interface in its own repository, completely separate from the real version, while still using all + * of the prod implementations for the rest of the pipeline (interactors and onward). Looks + * something like this: + * ``` + * RealRepository + * │ + * ├──►RepositorySwitcher──►RealInteractor──►RealViewModel + * │ + * DemoRepository + * ``` + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class DeviceBasedSatelliteRepositorySwitcher +@Inject +constructor( + private val realImpl: RealDeviceBasedSatelliteRepository, + private val demoImpl: DemoDeviceBasedSatelliteRepository, + private val demoModeController: DemoModeController, + @Application scope: CoroutineScope, +) : DeviceBasedSatelliteRepository { + private val isDemoMode = + conflatedCallbackFlow { + val callback = + object : DemoMode { + override fun dispatchDemoCommand(command: String?, args: Bundle?) { + // Don't care + } + + override fun onDemoModeStarted() { + demoImpl.startProcessingCommands() + trySend(true) + } + + override fun onDemoModeFinished() { + demoImpl.stopProcessingCommands() + trySend(false) + } + } + + demoModeController.addCallback(callback) + awaitClose { demoModeController.removeCallback(callback) } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode) + + @VisibleForTesting + val activeRepo: StateFlow<DeviceBasedSatelliteRepository> = + isDemoMode + .mapLatest { isDemoMode -> + if (isDemoMode) { + demoImpl + } else { + realImpl + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl) + + override val connectionState: StateFlow<SatelliteConnectionState> = + activeRepo + .flatMapLatest { it.connectionState } + .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.connectionState.value) + + override val signalStrength: StateFlow<Int> = + activeRepo + .flatMapLatest { it.signalStrength } + .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.signalStrength.value) + + override val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean> = + activeRepo + .flatMapLatest { it.isSatelliteAllowedForCurrentLocation } + .stateIn( + scope, + SharingStarted.WhileSubscribed(), + realImpl.isSatelliteAllowedForCurrentLocation.value + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt new file mode 100644 index 000000000000..7ecc29bf46c8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteDataSource.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.data.demo + +import android.os.Bundle +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.demomode.DemoMode +import com.android.systemui.demomode.DemoModeController +import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** + * Reads the incoming demo commands and emits the satellite-related commands to [satelliteEvents] + * for the demo repository to consume. + */ +@SysUISingleton +class DemoDeviceBasedSatelliteDataSource +@Inject +constructor( + demoModeController: DemoModeController, + @Application scope: CoroutineScope, +) { + private val demoCommandStream = demoModeController.demoFlowForCommand(DemoMode.COMMAND_NETWORK) + private val _satelliteCommands = + demoCommandStream.map { args -> args.toSatelliteEvent() }.filterNotNull() + + /** A flow that emits the demo commands that are satellite-related. */ + val satelliteEvents = + _satelliteCommands.stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_VALUE) + + private fun Bundle.toSatelliteEvent(): DemoSatelliteEvent? { + val satellite = getString("satellite") ?: return null + if (satellite != "show") { + return null + } + + return DemoSatelliteEvent( + connectionState = getString("connection").toConnectionState(), + signalStrength = getString("level")?.toInt() ?: 0, + ) + } + + data class DemoSatelliteEvent( + val connectionState: SatelliteConnectionState, + val signalStrength: Int, + ) + + private fun String?.toConnectionState(): SatelliteConnectionState { + if (this == null) { + return SatelliteConnectionState.Unknown + } + return try { + // Lets people use "connected" on the command line and have it be correctly converted + // to [SatelliteConnectionState.Connected] with a capital C. + SatelliteConnectionState.valueOf(this.replaceFirstChar { it.uppercase() }) + } catch (e: IllegalArgumentException) { + SatelliteConnectionState.Unknown + } + } + + private companion object { + val DEFAULT_VALUE = DemoSatelliteEvent(SatelliteConnectionState.Unknown, signalStrength = 0) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt new file mode 100644 index 000000000000..56034f08503d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.data.demo + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository +import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch + +/** A satellite repository that represents the latest satellite values sent via demo mode. */ +@SysUISingleton +class DemoDeviceBasedSatelliteRepository +@Inject +constructor( + private val dataSource: DemoDeviceBasedSatelliteDataSource, + @Application private val scope: CoroutineScope, +) : DeviceBasedSatelliteRepository { + private var demoCommandJob: Job? = null + + override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown) + override val signalStrength = MutableStateFlow(0) + override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(true) + + fun startProcessingCommands() { + demoCommandJob = + scope.launch { dataSource.satelliteEvents.collect { event -> processEvent(event) } } + } + + fun stopProcessingCommands() { + demoCommandJob?.cancel() + } + + private fun processEvent(event: DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent) { + connectionState.value = event.connectionState + signalStrength.value = event.signalStrength + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt index 3e3ea855ccf7..12f252d215a9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt @@ -31,7 +31,7 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.log.core.MessageInitializer import com.android.systemui.log.core.MessagePrinter import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog -import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository +import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Supported @@ -50,12 +50,14 @@ import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext @@ -134,7 +136,7 @@ constructor( @Application private val scope: CoroutineScope, @OemSatelliteInputLog private val logBuffer: LogBuffer, private val systemClock: SystemClock, -) : DeviceBasedSatelliteRepository { +) : RealDeviceBasedSatelliteRepository { private val satelliteManager: SatelliteManager? @@ -200,10 +202,12 @@ constructor( } override val connectionState = - satelliteSupport.whenSupported( - supported = ::connectionStateFlow, - orElse = flowOf(SatelliteConnectionState.Off) - ) + satelliteSupport + .whenSupported( + supported = ::connectionStateFlow, + orElse = flowOf(SatelliteConnectionState.Off) + ) + .stateIn(scope, SharingStarted.Eagerly, SatelliteConnectionState.Off) // By using the SupportedSatelliteManager here, we expect registration never to fail private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> = @@ -227,7 +231,9 @@ constructor( .flowOn(bgDispatcher) override val signalStrength = - satelliteSupport.whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0)) + satelliteSupport + .whenSupported(supported = ::signalStrengthFlow, orElse = flowOf(0)) + .stateIn(scope, SharingStarted.Eagerly, 0) // By using the SupportedSatelliteManager here, we expect registration never to fail private fun signalStrengthFlow(sm: SupportedSatelliteManager) = @@ -243,11 +249,17 @@ constructor( try { sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb) registered = true + logBuffer.i { "Registered for signal strength successfully" } } catch (e: Exception) { logBuffer.e("error registering for signal strength", e) } - awaitClose { if (registered) sm.unregisterForNtnSignalStrengthChanged(cb) } + awaitClose { + if (registered) { + sm.unregisterForNtnSignalStrengthChanged(cb) + logBuffer.i { "Unregistered for signal strength successfully" } + } + } } .flowOn(bgDispatcher) @@ -312,8 +324,8 @@ constructor( } companion object { - // TTL for satellite polling is one hour - const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 60 + // TTL for satellite polling is twenty minutes + const val POLLING_INTERVAL_MS: Long = 1000 * 60 * 20 // Let the system boot up and stabilize before we check for system support const val MIN_UPTIME: Long = 1000 * 60 diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt index 51c053ee284a..5b954b272044 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt @@ -19,6 +19,9 @@ package com.android.systemui.statusbar.pipeline.satellite.domain.interactor import com.android.internal.telephony.flags.Flags 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.statusbar.pipeline.dagger.OemSatelliteInputLog import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState @@ -45,6 +48,7 @@ constructor( deviceProvisioningInteractor: DeviceProvisioningInteractor, wifiInteractor: WifiInteractor, @Application scope: CoroutineScope, + @OemSatelliteInputLog private val logBuffer: LogBuffer, ) { /** Must be observed by any UI showing Satellite iconography */ val isSatelliteAllowed = @@ -79,25 +83,52 @@ constructor( val isWifiActive: Flow<Boolean> = wifiInteractor.wifiNetwork.map { it is WifiNetworkModel.Active } + private val allConnectionsOos = + iconsInteractor.icons.aggregateOver( + selector = { intr -> + combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) { + isInService, + isEmergencyOnly, + isNtn -> + !isInService && !isEmergencyOnly && !isNtn + } + }, + defaultValue = true, // no connections == everything is OOS + ) { isOosAndNotEmergencyAndNotSatellite -> + isOosAndNotEmergencyAndNotSatellite.all { it } + } + /** When all connections are considered OOS, satellite connectivity is potentially valid */ val areAllConnectionsOutOfService = if (Flags.oemEnabledSatelliteFlag()) { - iconsInteractor.icons.aggregateOver( - selector = { intr -> - combine(intr.isInService, intr.isEmergencyOnly, intr.isNonTerrestrial) { - isInService, - isEmergencyOnly, - isNtn -> - !isInService && !(isEmergencyOnly || isNtn) - } - } - ) { isOosAndNotEmergencyOnlyOrSatellite -> - isOosAndNotEmergencyOnlyOrSatellite.all { it } + combine( + allConnectionsOos, + iconsInteractor.isDeviceInEmergencyCallsOnlyMode, + ) { connectionsOos, deviceEmergencyOnly -> + logBuffer.log( + TAG, + LogLevel.INFO, + { + bool1 = connectionsOos + bool2 = deviceEmergencyOnly + }, + { + "Updating OOS status. allConnectionsOOs=$bool1 " + + "deviceEmergencyOnly=$bool2" + }, + ) + // If no connections exist, or all are OOS, then we look to the device-based + // service state to detect if any calls are possible + connectionsOos && !deviceEmergencyOnly } } else { flowOf(false) } .stateIn(scope, SharingStarted.WhileSubscribed(), true) + + companion object { + const val TAG = "DeviceBasedSatelliteInteractor" + } } /** @@ -106,12 +137,22 @@ constructor( * * Provides a way to connect the reactivity of the top-level flow with the reactivity of an * arbitrarily-defined relationship ([selector]) from R to the flow that R exposes. + * + * [defaultValue] allows for a default value to be used if there are no leaf nodes after applying + * [selector]. E.g., if there are no mobile connections, assume that there is no service. */ @OptIn(ExperimentalCoroutinesApi::class) private inline fun <R, reified S, T> Flow<List<R>>.aggregateOver( crossinline selector: (R) -> Flow<S>, - crossinline transform: (Array<S>) -> T + defaultValue: T, + crossinline transform: (Array<S>) -> T, ): Flow<T> { return map { list -> list.map { selector(it) } } - .flatMapLatest { newFlows -> combine(newFlows) { newVals -> transform(newVals) } } + .flatMapLatest { newFlows -> + if (newFlows.isEmpty()) { + flowOf(defaultValue) + } else { + combine(newFlows) { newVals -> transform(newVals) } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt index cc87e8a45d13..0a6e95eee127 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED @@ -81,12 +82,12 @@ constructor( ) : CollapsedStatusBarViewModel { override val isTransitioningFromLockscreenToOccluded: StateFlow<Boolean> = keyguardTransitionInteractor - .isInTransition(LOCKSCREEN, OCCLUDED) + .isInTransition(Edge.create(from = LOCKSCREEN, to = OCCLUDED)) .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), initialValue = false) override val transitionFromLockscreenToDreamStartedEvent: Flow<Unit> = keyguardTransitionInteractor - .transition(LOCKSCREEN, DREAMING) + .transition(Edge.create(from = LOCKSCREEN, to = DREAMING)) .filter { it.transitionState == TransitionState.STARTED } .map {} 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..8b48bd32e089 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry +import com.android.systemui.util.Compile import java.io.PrintWriter import javax.inject.Inject @@ -30,12 +31,14 @@ import javax.inject.Inject * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager */ @SysUISingleton -class AvalancheController @Inject constructor( +class AvalancheController +@Inject +constructor( dumpManager: DumpManager, ) : Dumpable { private val tag = "AvalancheController" - private val debug = false + private val debug = Compile.IS_DEBUG && Log.isLoggable(tag, Log.DEBUG) // HUN showing right now, in the floating state where full shade is hidden, on launcher or AOD @VisibleForTesting var headsUpEntryShowing: HeadsUpEntry? = null @@ -79,7 +82,7 @@ class AvalancheController @Inject constructor( val fn = "[$label] => AvalancheController.update [${getKey(entry)}]" if (entry == null) { log { "Entry is NULL, stop update." } - return; + return } if (debug) { debugRunnableLabelMap[runnable] = label @@ -106,7 +109,10 @@ class AvalancheController @Inject constructor( if (isOnlyNextEntry) { // HeadsUpEntry.updateEntry recursively calls AvalancheController#update // and goes to the isShowing case above - headsUpEntryShowing!!.updateEntry(false, "avalanche duration update") + headsUpEntryShowing!!.updateEntry( + /* updatePostTime= */ false, + /* updateEarliestRemovalTime= */ false, + /* reason= */ "avalanche duration update") } } logState("after $fn") @@ -142,9 +148,12 @@ class AvalancheController @Inject constructor( } else if (isShowing(entry)) { log { "$fn => [remove showing ${getKey(entry)}]" } previousHunKey = getKey(headsUpEntryShowing) - + // Show the next HUN before removing this one, so that we don't tell listeners + // onHeadsUpPinnedModeChanged, which causes + // NotificationPanelViewController.updateTouchableRegion to hide the window while the + // HUN is animating out, resulting in a flicker. + showNext() runnable.run() - showNextAfterRemove() } else { log { "$fn => [removing untracked ${getKey(entry)}]" } } @@ -247,12 +256,13 @@ class AvalancheController @Inject constructor( } } - private fun showNextAfterRemove() { + private fun showNext() { log { "SHOW NEXT" } headsUpEntryShowing = null if (nextList.isEmpty()) { log { "NO MORE TO SHOW" } + previousHunKey = "" return } @@ -293,17 +303,21 @@ class AvalancheController @Inject constructor( private fun getStateStr(): String { return "SHOWING: [${getKey(headsUpEntryShowing)}]" + - "\nPREVIOUS: [$previousHunKey]" + - "\nNEXT LIST: $nextListStr" + - "\nNEXT MAP: $nextMapStr" + - "\nDROPPED: $dropSetStr" + "\nPREVIOUS: [$previousHunKey]" + + "\nNEXT LIST: $nextListStr" + + "\nNEXT MAP: $nextMapStr" + + "\nDROPPED: $dropSetStr" } private fun logState(reason: String) { - log { "\n================================================================================="} + log { + "\n=================================================================================" + } log { "STATE $reason" } log { getStateStr() } - log { "=================================================================================\n"} + log { + "=================================================================================\n" + } } private val dropSetStr: String diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index b8318a7dfb61..a7fe49b54602 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -39,6 +39,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; +import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun; import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor; import com.android.systemui.util.ListenerSet; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -114,7 +115,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { mUiEventLogger = uiEventLogger; mAvalancheController = avalancheController; Resources resources = context.getResources(); - mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); + mMinimumDisplayTime = NotificationThrottleHun.isEnabled() + ? 500 : resources.getInteger(R.integer.heads_up_notification_minimum_time); mStickyForSomeTimeAutoDismissTime = resources.getInteger( R.integer.sticky_heads_up_notification_time); mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay); @@ -765,11 +767,23 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * @param updatePostTime whether or not to refresh the post time */ public void updateEntry(boolean updatePostTime, @Nullable String reason) { + updateEntry(updatePostTime, /* updateEarliestRemovalTime= */ true, reason); + } + + /** + * Updates an entry's removal time. + * @param updatePostTime whether or not to refresh the post time + * @param updateEarliestRemovalTime whether this update should further delay removal + */ + public void updateEntry(boolean updatePostTime, boolean updateEarliestRemovalTime, + @Nullable String reason) { Runnable runnable = () -> { mLogger.logUpdateEntry(mEntry, updatePostTime, reason); final long now = mSystemClock.elapsedRealtime(); - mEarliestRemovalTime = now + mMinimumDisplayTime; + if (updateEarliestRemovalTime) { + mEarliestRemovalTime = now + mMinimumDisplayTime; + } if (updatePostTime) { mPostTime = Math.max(mPostTime, now); @@ -785,7 +799,9 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { FinishTimeUpdater finishTimeCalculator = () -> { final long finishTime = calculateFinishTime(); final long now = mSystemClock.elapsedRealtime(); - final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime); + final long timeLeft = NotificationThrottleHun.isEnabled() + ? Math.max(finishTime, mEarliestRemovalTime) - now + : Math.max(finishTime - now, mMinimumDisplayTime); return timeLeft; }; scheduleAutoRemovalCallback(finishTimeCalculator, "updateEntry (not sticky)"); @@ -818,13 +834,6 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } public int compareNonTimeFields(HeadsUpEntry headsUpEntry) { - boolean isPinned = mEntry.isRowPinned(); - boolean otherPinned = headsUpEntry.mEntry.isRowPinned(); - if (isPinned && !otherPinned) { - return -1; - } else if (!isPinned && otherPinned) { - return 1; - } boolean selfFullscreen = hasFullScreenIntent(mEntry); boolean otherFullscreen = hasFullScreenIntent(headsUpEntry.mEntry); if (selfFullscreen && !otherFullscreen) { @@ -851,6 +860,13 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } public int compareTo(@NonNull HeadsUpEntry headsUpEntry) { + boolean isPinned = mEntry.isRowPinned(); + boolean otherPinned = headsUpEntry.mEntry.isRowPinned(); + if (isPinned && !otherPinned) { + return -1; + } else if (!isPinned && otherPinned) { + return 1; + } int nonTimeCompareResult = compareNonTimeFields(headsUpEntry); if (nonTimeCompareResult != 0) { return nonTimeCompareResult; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java index 37eda6490ec2..92731037dc64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java @@ -17,11 +17,11 @@ package com.android.systemui.statusbar.policy; import android.annotation.Nullable; -import android.view.View; import androidx.annotation.NonNull; import com.android.systemui.Dumpable; +import com.android.systemui.animation.Expandable; import com.android.systemui.demomode.DemoMode; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -51,23 +51,23 @@ public interface BatteryController extends DemoMode, * * Can pass the view that triggered the request. */ - void setPowerSaveMode(boolean powerSave, @Nullable View view); + void setPowerSaveMode(boolean powerSave, @Nullable Expandable expandable); /** * Gets a reference to the last view used when called {@link #setPowerSaveMode}. */ @Nullable - default WeakReference<View> getLastPowerSaverStartView() { + default WeakReference<Expandable> getLastPowerSaverStartExpandable() { return null; } /** * Clears the last view used when called {@link #setPowerSaveMode}. * - * Immediately after calling this, a call to {@link #getLastPowerSaverStartView()} should return - * {@code null}. + * Immediately after calling this, a call to {@link #getLastPowerSaverStartExpandable()} should + * return {@code null}. */ - default void clearLastPowerSaverStartView() {} + default void clearLastPowerSaverStartExpandable() {} /** * Returns {@code true} if the device is currently plugged in. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index dab27bbf6b4b..6012ecd5a7f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -37,7 +37,6 @@ import android.os.Handler; import android.os.PowerManager; import android.os.PowerSaveState; import android.util.IndentingPrintWriter; -import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -48,6 +47,7 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.settingslib.fuelgauge.Estimate; import com.android.settingslib.utils.PowerUtil; import com.android.systemui.Dumpable; +import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -110,9 +110,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC private boolean mFetchingEstimate = false; // Use AtomicReference because we may request it from a different thread - // Use WeakReference because we are keeping a reference to a View that's not as long lived - // as this controller. - private AtomicReference<WeakReference<View>> mPowerSaverStartView = new AtomicReference<>(); + // Use WeakReference because we are keeping a reference to an Expandable that's not as long + // lived as this controller. + private AtomicReference<WeakReference<Expandable>> mPowerSaverStartExpandable = + new AtomicReference<>(); @VisibleForTesting public BatteryControllerImpl( @@ -196,20 +197,20 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC } @Override - public void setPowerSaveMode(boolean powerSave, View view) { - if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view)); + public void setPowerSaveMode(boolean powerSave, Expandable expandable) { + if (powerSave) mPowerSaverStartExpandable.set(new WeakReference<>(expandable)); BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true, SAVER_ENABLED_QS); } @Override - public WeakReference<View> getLastPowerSaverStartView() { - return mPowerSaverStartView.get(); + public WeakReference<Expandable> getLastPowerSaverStartExpandable() { + return mPowerSaverStartExpandable.get(); } @Override - public void clearLastPowerSaverStartView() { - mPowerSaverStartView.set(null); + public void clearLastPowerSaverStartExpandable() { + mPowerSaverStartExpandable.set(null); } @Override @@ -543,4 +544,4 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC public boolean isChargingSourceDock() { return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_DOCK; } -} +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt index 5da01e23e268..3e0118007c3c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModel.kt @@ -22,6 +22,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor +import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import javax.inject.Inject @@ -46,6 +47,7 @@ class KeyguardStatusBarViewModel @Inject constructor( @Application scope: CoroutineScope, + headsUpNotificationInteractor: HeadsUpNotificationInteractor, keyguardInteractor: KeyguardInteractor, keyguardStatusBarInteractor: KeyguardStatusBarInteractor, batteryController: BatteryController, @@ -55,8 +57,9 @@ constructor( combine( keyguardInteractor.isDozing, keyguardInteractor.statusBarState, - ) { isDozing, statusBarState -> - !isDozing && statusBarState == StatusBarState.KEYGUARD + headsUpNotificationInteractor.showHeadsUpStatusBar, + ) { isDozing, statusBarState, showHeadsUpStatusBar -> + !isDozing && statusBarState == StatusBarState.KEYGUARD && !showHeadsUpStatusBar } .stateIn(scope, SharingStarted.WhileSubscribed(), false) diff --git a/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java b/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java new file mode 100644 index 000000000000..efeb2f919b56 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/theme/CustomDynamicColors.java @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.theme; + +import com.google.ux.material.libmonet.dynamiccolor.ContrastCurve; +import com.google.ux.material.libmonet.dynamiccolor.DynamicColor; +import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors; +import com.google.ux.material.libmonet.dynamiccolor.ToneDeltaPair; +import com.google.ux.material.libmonet.dynamiccolor.TonePolarity; + +class CustomDynamicColors { + private final MaterialDynamicColors mMdc; + + CustomDynamicColors(boolean isExtendedFidelity) { + this.mMdc = new MaterialDynamicColors(isExtendedFidelity); + } + + // CLOCK COLORS + + public DynamicColor widgetBackground() { + return new DynamicColor( + /* name= */ "widget_background", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> s.isDark ? 20.0 : 95.0, + /* isBackground= */ true, + /* background= */ null, + /* secondBackground= */ null, + /* contrastCurve= */ null, + /* toneDeltaPair= */ null); + } + + public DynamicColor clockHour() { + return new DynamicColor( + /* name= */ "clock_hour", + /* palette= */ (s) -> s.secondaryPalette, + /* tone= */ (s) -> s.isDark ? 30.0 : 60.0, + /* isBackground= */ false, + /* background= */ (s) -> widgetBackground(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 4.0, 5.0, 15.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(clockHour(), clockMinute(), 10.0, TonePolarity.DARKER, + false)); + } + + public DynamicColor clockMinute() { + return new DynamicColor( + /* name= */ "clock_minute", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> s.isDark ? 40.0 : 90.0, + /* isBackground= */ false, + /* background= */ (s) -> widgetBackground(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 6.5, 10.0, 15.0), + /* toneDeltaPair= */ null); + } + + public DynamicColor clockSecond() { + return new DynamicColor( + /* name= */ "clock_second", + /* palette= */ (s) -> s.tertiaryPalette, + /* tone= */ (s) -> s.isDark ? 40.0 : 90.0, + /* isBackground= */ false, + /* background= */ (s) -> widgetBackground(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 5.0, 70.0, 11.0), + /* toneDeltaPair= */ null); + } + + public DynamicColor weatherTemp() { + return new DynamicColor( + /* name= */ "clock_second", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> s.isDark ? 55.0 : 80.0, + /* isBackground= */ false, + /* background= */ (s) -> widgetBackground(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 5.0, 70.0, 11.0), + /* toneDeltaPair= */ null); + } + + // THEME APP ICONS + + public DynamicColor themeApp() { + return new DynamicColor( + /* name= */ "theme_app", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> s.isDark ? 90.0 : 30.0, // Adjusted values + /* isBackground= */ true, + /* background= */ null, + /* secondBackground= */ null, + /* contrastCurve= */ null, + /* toneDeltaPair= */ null); + } + + public DynamicColor onThemeApp() { + return new DynamicColor( + /* name= */ "on_theme_app", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> s.isDark ? 40.0 : 80.0, // Adjusted values + /* isBackground= */ false, + /* background= */ (s) -> themeApp(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 7.0, 10.0), + /* toneDeltaPair= */ null); + } + + public DynamicColor themeAppRing() { + return new DynamicColor( + /* name= */ "theme_app_ring", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> 70.0, + /* isBackground= */ true, + /* background= */ null, + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0), + /* toneDeltaPair= */ null); + } + + public DynamicColor themeNotif() { + return new DynamicColor( + /* name= */ "theme_notif", + /* palette= */ (s) -> s.tertiaryPalette, + /* tone= */ (s) -> s.isDark ? 80.0 : 90.0, + /* isBackground= */ false, + /* background= */ (s) -> themeAppRing(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(themeNotif(), themeAppRing(), 10.0, TonePolarity.NEARER, + false)); + } + + // SUPER G COLORS + + public DynamicColor brandA() { + return new DynamicColor( + /* name= */ "brand_a", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> s.isDark ? 40.0 : 80.0, + /* isBackground= */ true, + /* background= */ (s) -> mMdc.surfaceContainerLow(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 7.0, 17.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(brandA(), brandB(), 10.0, TonePolarity.NEARER, false)); + } + + public DynamicColor brandB() { + return new DynamicColor( + /* name= */ "brand_b", + /* palette= */ (s) -> s.secondaryPalette, + /* tone= */ (s) -> s.isDark ? 70.0 : 98.0, + /* isBackground= */ true, + /* background= */ (s) -> mMdc.surfaceContainerLow(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 3.0, 6.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(brandB(), brandC(), 10.0, TonePolarity.NEARER, false)); + } + + public DynamicColor brandC() { + return new DynamicColor( + /* name= */ "brand_c", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> s.isDark ? 50.0 : 60.0, + /* isBackground= */ false, + /* background= */ (s) -> mMdc.surfaceContainerLow(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.0, 9.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(brandC(), brandD(), 10.0, TonePolarity.NEARER, false)); + } + + public DynamicColor brandD() { + return new DynamicColor( + /* name= */ "brand_d", + /* palette= */ (s) -> s.tertiaryPalette, + /* tone= */ (s) -> s.isDark ? 59.0 : 90.0, + /* isBackground= */ false, + /* background= */ (s) -> mMdc.surfaceContainerLow(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.0, 13.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(brandD(), brandA(), 10.0, TonePolarity.NEARER, false)); + } + + // QUICK SETTING TIILES + + public DynamicColor underSurface() { + return new DynamicColor( + /* name= */ "under_surface", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> 0.0, + /* isBackground= */ true, + /* background= */ null, + /* secondBackground= */ null, + /* contrastCurve= */ null, + /* toneDeltaPair= */ null); + } + + public DynamicColor shadeActive() { + return new DynamicColor( + /* name= */ "shade_active", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> 90.0, + /* isBackground= */ false, + /* background= */ (s) -> underSurface(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 3.0, 4.5, 7.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(shadeActive(), shadeInactive(), 30.0, TonePolarity.LIGHTER, + false)); + } + + public DynamicColor onShadeActive() { + return new DynamicColor( + /* name= */ "on_shade_active", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> 10.0, + /* isBackground= */ false, + /* background= */ (s) -> shadeActive(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(onShadeActive(), onShadeActiveVariant(), 20.0, + TonePolarity.NEARER, false)); + } + + public DynamicColor onShadeActiveVariant() { + return new DynamicColor( + /* name= */ "on_shade_active_variant", + /* palette= */ (s) -> s.primaryPalette, + /* tone= */ (s) -> 30.0, + /* isBackground= */ false, + /* background= */ (s) -> shadeActive(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(onShadeActiveVariant(), onShadeActive(), 20.0, + TonePolarity.NEARER, false)); + } + + public DynamicColor shadeInactive() { + return new DynamicColor( + /* name= */ "shade_inactive", + /* palette= */ (s) -> s.neutralPalette, + /* tone= */ (s) -> 20.0, + /* isBackground= */ true, + /* background= */ (s) -> underSurface(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0), + /* toneDeltaPair= */(s) -> new ToneDeltaPair(shadeInactive(), shadeDisabled(), 15.0, + TonePolarity.LIGHTER, false)); + } + + public DynamicColor onShadeInactive() { + return new DynamicColor( + /* name= */ "on_shade_inactive", + /* palette= */ (s) -> s.neutralVariantPalette, + /* tone= */ (s) -> 90.0, + /* isBackground= */ true, + /* background= */ (s) -> shadeInactive(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(onShadeInactive(), onShadeInactiveVariant(), 10.0, + TonePolarity.NEARER, false)); + } + + public DynamicColor onShadeInactiveVariant() { + return new DynamicColor( + /* name= */ "on_shade_inactive_variant", + /* palette= */ (s) -> s.neutralVariantPalette, + /* tone= */ (s) -> 80.0, + /* isBackground= */ false, + /* background= */ (s) -> shadeInactive(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 4.5, 7.0, 11.0), + /* toneDeltaPair= */ + (s) -> new ToneDeltaPair(onShadeInactiveVariant(), onShadeInactive(), 10.0, + TonePolarity.NEARER, false)); + } + + public DynamicColor shadeDisabled() { + return new DynamicColor( + /* name= */ "shade_disabled", + /* palette= */ (s) -> s.neutralPalette, + /* tone= */ (s) -> 4.0, + /* isBackground= */ false, + /* background= */ (s) -> underSurface(), + /* secondBackground= */ null, + /* contrastCurve= */ new ContrastCurve(1.0, 1.0, 1.0, 1.0), + /* toneDeltaPair= */ null); + } + + public DynamicColor overviewBackground() { + return new DynamicColor( + /* name= */ "overview_background", + /* palette= */ (s) -> s.neutralVariantPalette, + /* tone= */ (s) -> s.isDark ? 80.0 : 35.0, + /* isBackground= */ true, + /* background= */ null, + /* secondBackground= */ null, + /* contrastCurve= */null, + /* toneDeltaPair= */ null); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt index a983d2f9b78e..35187597bd83 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt +++ b/packages/SystemUI/src/com/android/systemui/theme/DynamicColors.kt @@ -103,5 +103,33 @@ class DynamicColors { Pair.create("on_tertiary_fixed_variant", mdc.onTertiaryFixedVariant()), ) } + + @JvmStatic + fun getCustomColorsMapped(isExtendedFidelity: Boolean): List<Pair<String, DynamicColor>> { + val customMdc = CustomDynamicColors(isExtendedFidelity) + return arrayListOf( + Pair.create("widget_background", customMdc.widgetBackground()), + Pair.create("clock_hour", customMdc.clockHour()), + Pair.create("clock_minute", customMdc.clockMinute()), + Pair.create("clock_second", customMdc.weatherTemp()), + Pair.create("theme_app", customMdc.themeApp()), + Pair.create("on_theme_app", customMdc.onThemeApp()), + Pair.create("theme_app_ring", customMdc.themeAppRing()), + Pair.create("on_theme_app_ring", customMdc.themeNotif()), + Pair.create("brand_a", customMdc.brandA()), + Pair.create("brand_b", customMdc.brandB()), + Pair.create("brand_c", customMdc.brandC()), + Pair.create("brand_d", customMdc.brandD()), + Pair.create("under_surface", customMdc.underSurface()), + Pair.create("shade_active", customMdc.shadeActive()), + Pair.create("on_shade_active", customMdc.onShadeActive()), + Pair.create("on_shade_active_variant", customMdc.onShadeActiveVariant()), + Pair.create("shade_inactive", customMdc.shadeInactive()), + Pair.create("on_shade_inactive", customMdc.onShadeInactive()), + Pair.create("on_shade_inactive_variant", customMdc.onShadeInactiveVariant()), + Pair.create("shade_disabled", customMdc.shadeDisabled()), + Pair.create("overview_background", customMdc.overviewBackground()) + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 5c3bbb7ca253..d256c4acd955 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -56,6 +56,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.SparseArray; import android.util.SparseIntArray; @@ -84,6 +85,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.util.kotlin.JavaAdapter; import com.android.systemui.util.settings.SecureSettings; +import com.google.ux.material.libmonet.dynamiccolor.DynamicColor; import com.google.ux.material.libmonet.dynamiccolor.MaterialDynamicColors; import org.json.JSONException; @@ -631,29 +633,33 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { protected FabricatedOverlay createDynamicOverlay() { FabricatedOverlay overlay = newFabricatedOverlay("dynamic"); - assignDynamicPaletteToOverlay(overlay, true /* isDark */); - assignDynamicPaletteToOverlay(overlay, false /* isDark */); - assignFixedColorsToOverlay(overlay); + //Themed Colors + assignColorsToOverlay(overlay, DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled), + false); + // Fixed Colors + assignColorsToOverlay(overlay, DynamicColors.getFixedColorsMapped(mIsFidelityEnabled), + true); + //Custom Colors + assignColorsToOverlay(overlay, DynamicColors.getCustomColorsMapped(mIsFidelityEnabled), + false); return overlay; } - private void assignDynamicPaletteToOverlay(FabricatedOverlay overlay, boolean isDark) { - String suffix = isDark ? "dark" : "light"; - ColorScheme scheme = isDark ? mDarkColorScheme : mLightColorScheme; - DynamicColors.allDynamicColorsMapped(mIsFidelityEnabled).forEach(p -> { - String resourceName = "android:color/system_" + p.first + "_" + suffix; - int colorValue = p.second.getArgb(scheme.getMaterialScheme()); - overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue, - null /* configuration */); - }); - } + private void assignColorsToOverlay(FabricatedOverlay overlay, + List<Pair<String, DynamicColor>> colors, Boolean isFixed) { + colors.forEach(p -> { + String prefix = "android:color/system_" + p.first; - private void assignFixedColorsToOverlay(FabricatedOverlay overlay) { - DynamicColors.getFixedColorsMapped(mIsFidelityEnabled).forEach(p -> { - String resourceName = "android:color/system_" + p.first; - int colorValue = p.second.getArgb(mLightColorScheme.getMaterialScheme()); - overlay.setResourceValue(resourceName, TYPE_INT_COLOR_ARGB8, colorValue, - null /* configuration */); + if (isFixed) { + overlay.setResourceValue(prefix, TYPE_INT_COLOR_ARGB8, + p.second.getArgb(mLightColorScheme.getMaterialScheme()), null); + return; + } + + overlay.setResourceValue(prefix + "_light", TYPE_INT_COLOR_ARGB8, + p.second.getArgb(mLightColorScheme.getMaterialScheme()), null); + overlay.setResourceValue(prefix + "_dark", TYPE_INT_COLOR_ARGB8, + p.second.getArgb(mDarkColorScheme.getMaterialScheme()), null); }); } diff --git a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt index ca5ea3bc1caa..135edfcb6a42 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/FullscreenLightRevealAnimation.kt @@ -32,6 +32,7 @@ import android.view.SurfaceControlViewHost import android.view.SurfaceSession import android.view.WindowManager import android.view.WindowlessWindowManager +import androidx.annotation.WorkerThread import com.android.app.tracing.traceSection import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -235,8 +236,10 @@ constructor( } private inner class RotationWatcher : RotationChangeProvider.RotationListener { + @WorkerThread override fun onRotationChanged(newRotation: Int) { traceSection("$TAG#onRotationChanged") { + ensureInBackground() if (currentRotation != newRotation) { currentRotation = newRotation scrimView?.revealEffect = lightRevealEffectFactory(currentRotation) diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 37be1c6aa73d..a817b31070a1 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.user.data.repository import android.annotation.SuppressLint +import android.annotation.UserIdInt import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle @@ -107,6 +108,22 @@ interface UserRepository { fun isSimpleUserSwitcher(): Boolean fun isUserSwitcherEnabled(): Boolean + + /** + * Returns the user ID of the "main user" of the device. This user may have access to certain + * features which are limited to at most one user. There will never be more than one main user + * on a device. + * + * <p>Currently, on most form factors the first human user on the device will be the main user; + * in the future, the concept may be transferable, so a different user (or even no user at all) + * may be designated the main user instead. On other form factors there might not be a main + * user. + * + * <p> When the device doesn't have a main user, this will return {@code null}. + * + * @see [UserManager.getMainUser] + */ + @UserIdInt suspend fun getMainUserId(): Int? } @SysUISingleton @@ -239,6 +256,10 @@ constructor( return _userSwitcherSettings.value.isUserSwitcherEnabled } + override suspend fun getMainUserId(): Int? { + return withContext(backgroundDispatcher) { manager.mainUser?.identifier } + } + private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt index bfed0c44f6b7..0a1724c189c8 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt @@ -70,8 +70,10 @@ constructor( val isGuestUserResetting: Boolean = repository.isGuestUserResetting init { - resumeSessionReceiver.register() - resetOrExitSessionReceiver.register() + if (applicationContext.userId == UserHandle.USER_SYSTEM) { + resumeSessionReceiver.register() + resetOrExitSessionReceiver.register() + } } /** Notifies that the device has finished booting. */ diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt index 38b381ac543e..59c819d41493 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/SelectedUserInteractor.kt @@ -2,6 +2,7 @@ package com.android.systemui.user.domain.interactor import android.annotation.UserIdInt import android.content.pm.UserInfo +import android.os.UserManager import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Flags.refactorGetCurrentUser import com.android.systemui.dagger.SysUISingleton @@ -38,4 +39,23 @@ class SelectedUserInteractor @Inject constructor(private val repository: UserRep KeyguardUpdateMonitor.getCurrentUser() } } + + /** + * Returns the user ID of the "main user" of the device. This user may have access to certain + * features which are limited to at most one user. There will never be more than one main user + * on a device. + * + * <p>Currently, on most form factors the first human user on the device will be the main user; + * in the future, the concept may be transferable, so a different user (or even no user at all) + * may be designated the main user instead. On other form factors there might not be a main + * user. + * + * <p> When the device doesn't have a main user, this will return {@code null}. + * + * @see [UserManager.getMainUser] + */ + @UserIdInt + fun getMainUserId(): Int? { + return repository.mainUserId + } } diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt index 93396516add7..516cb46ec6ee 100644 --- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt @@ -533,7 +533,7 @@ constructor( targetUserId = targetUserId, ::showDialog, ::dismissDialog, - ::selectUser, + ::switchUser ) } } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt index b3008856d370..a2759c6bf470 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BooleanFlowOperators.kt @@ -28,11 +28,23 @@ object BooleanFlowOperators { * * Usage: * ``` - * val result = and(flow1, flow2) + * val result = allOf(flow1, flow2) * ``` */ - fun and(vararg flows: Flow<Boolean>): Flow<Boolean> = - combine(flows.asIterable()) { values -> values.all { it } }.distinctUntilChanged() + fun allOf(vararg flows: Flow<Boolean>): Flow<Boolean> = flows.asIterable().all() + + /** + * Logical AND operator for boolean flows. Will collect all flows and [combine] them to + * determine the result. + */ + fun Array<Flow<Boolean>>.all(): Flow<Boolean> = allOf(*this) + + /** + * Logical AND operator for boolean flows. Will collect all flows and [combine] them to + * determine the result. + */ + fun Iterable<Flow<Boolean>>.all(): Flow<Boolean> = + combine(this) { values -> values.all { it } }.distinctUntilChanged() /** * Logical NOT operator for a boolean flow. @@ -48,6 +60,36 @@ object BooleanFlowOperators { * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to * determine the result. */ - fun or(vararg flows: Flow<Boolean>): Flow<Boolean> = - combine(flows.asIterable()) { values -> values.any { it } }.distinctUntilChanged() + fun anyOf(vararg flows: Flow<Boolean>): Flow<Boolean> = flows.asIterable().any() + + /** + * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to + * determine the result. + */ + fun Array<Flow<Boolean>>.any(): Flow<Boolean> = anyOf(*this) + + /** + * Logical OR operator for a boolean flow. Will collect all flows and [combine] them to + * determine the result. + */ + fun Iterable<Flow<Boolean>>.any(): Flow<Boolean> = + combine(this) { values -> values.any { it } }.distinctUntilChanged() + + /** + * Returns a Flow that produces `true` when all input flows are producing `false`, otherwise + * produces `false`. + */ + fun noneOf(vararg flows: Flow<Boolean>): Flow<Boolean> = not(anyOf(*flows)) + + /** + * Returns a Flow that produces `true` when all input flows are producing `false`, otherwise + * produces `false`. + */ + fun Array<Flow<Boolean>>.none(): Flow<Boolean> = noneOf(*this) + + /** + * Returns a Flow that produces `true` when all input flows are producing `false`, otherwise + * produces `false`. + */ + fun Iterable<Flow<Boolean>>.none(): Flow<Boolean> = not(any()) } diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt index 46ce5f2623de..1ec86a4d1dfc 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt @@ -97,3 +97,12 @@ fun <T> collectFlow( fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> { return combine(flow1, flow2, bifunction) } + +fun <A, B, C, R> combineFlows( + flow1: Flow<A>, + flow2: Flow<B>, + flow3: Flow<C>, + trifunction: (A, B, C) -> R +): Flow<R> { + return combine(flow1, flow2, flow3, trifunction) +} 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/util/settings/SettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java deleted file mode 100644 index aeed78ad4df4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util.settings; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.content.ContentResolver; -import android.database.ContentObserver; -import android.net.Uri; -import android.provider.Settings; - -/** - * Used to interact with mainly with Settings.Global, but can also be used for Settings.System - * and Settings.Secure. To use the per-user System and Secure settings, {@link UserSettingsProxy} - * must be used instead. - * <p> - * This interface can be implemented to give instance method (instead of static method) versions - * of Settings.Global. It can be injected into class constructors and then faked or mocked as needed - * in tests. - * <p> - * You can ask for {@link GlobalSettings} to be injected as needed. - * <p> - * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, - * normally found on {@link ContentResolver} instances, unifying setting related actions in one - * place. - */ -public interface SettingsProxy { - - /** - * Returns the {@link ContentResolver} this instance was constructed with. - */ - ContentResolver getContentResolver(); - - /** - * Construct the content URI for a particular name/value pair, - * useful for monitoring changes with a ContentObserver. - * @param name to look up in the table - * @return the corresponding content URI, or null if not present - */ - Uri getUriFor(String name); - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * <p> - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserver(String name, ContentObserver settingsObserver) { - registerContentObserver(getUriFor(name), settingsObserver); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - */ - default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { - registerContentObserver(uri, false, settingsObserver); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - * <p> - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserver(String name, boolean notifyForDescendants, - ContentObserver settingsObserver) { - registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - */ - default void registerContentObserver(Uri uri, boolean notifyForDescendants, - ContentObserver settingsObserver) { - getContentResolver().registerContentObserver( - uri, notifyForDescendants, settingsObserver); - } - - /** See {@link ContentResolver#unregisterContentObserver(ContentObserver)}. */ - default void unregisterContentObserver(ContentObserver settingsObserver) { - getContentResolver().unregisterContentObserver(settingsObserver); - } - - /** - * Look up a name in the database. - * @param name to look up in the table - * @return the corresponding value, or null if not present - */ - @Nullable - String getString(String name); - - /** - * Store a name/value pair into the database. - * @param name to store - * @param value to associate with the name - * @return true if the value was set, false on database errors - */ - boolean putString(String name, String value); - - /** - * Store a name/value pair into the database. - * <p> - * The method takes an optional tag to associate with the setting - * which can be used to clear only settings made by your package and - * associated with this tag by passing the tag to {@link - * #resetToDefaults(String)}. Anyone can override - * the current tag. Also if another package changes the setting - * then the tag will be set to the one specified in the set call - * which can be null. Also any of the settings setters that do not - * take a tag as an argument effectively clears the tag. - * </p><p> - * For example, if you set settings A and B with tags T1 and T2 and - * another app changes setting A (potentially to the same value), it - * can assign to it a tag T3 (note that now the package that changed - * the setting is not yours). Now if you reset your changes for T1 and - * T2 only setting B will be reset and A not (as it was changed by - * another package) but since A did not change you are in the desired - * initial state. Now if the other app changes the value of A (assuming - * you registered an observer in the beginning) you would detect that - * the setting was changed by another app and handle this appropriately - * (ignore, set back to some value, etc). - * </p><p> - * Also the method takes an argument whether to make the value the - * default for this setting. If the system already specified a default - * value, then the one passed in here will <strong>not</strong> - * be set as the default. - * </p> - * - * @param name to store. - * @param value to associate with the name. - * @param tag to associate with the setting. - * @param makeDefault whether to make the value the default one. - * @return true if the value was set, false on database errors. - * - * @see #resetToDefaults(String) - * - */ - boolean putString(@NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault); - - /** - * Convenience function for retrieving a single secure settings value - * as an integer. Note that internally setting values are always - * stored as strings; this function converts the string to an integer - * for you. The default value will be returned if the setting is - * not defined or not an integer. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid integer. - */ - default int getInt(String name, int def) { - String v = getString(name); - try { - return v != null ? Integer.parseInt(v) : def; - } catch (NumberFormatException e) { - return def; - } - } - - /** - * Convenience function for retrieving a single secure settings value - * as an integer. Note that internally setting values are always - * stored as strings; this function converts the string to an integer - * for you. - * <p> - * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not an integer. - * - * @return The setting's current value. - */ - default int getInt(String name) - throws Settings.SettingNotFoundException { - String v = getString(name); - try { - return Integer.parseInt(v); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** - * Convenience function for updating a single settings value as an - * integer. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putInt(String name, int value) { - return putString(name, Integer.toString(value)); - } - - /** - * Convenience function for retrieving a single secure settings value - * as a boolean. Note that internally setting values are always - * stored as strings; this function converts the string to a boolean - * for you. The default value will be returned if the setting is - * not defined or not a boolean. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid boolean. - */ - default boolean getBool(String name, boolean def) { - return getInt(name, def ? 1 : 0) != 0; - } - - /** - * Convenience function for retrieving a single secure settings value - * as a boolean. Note that internally setting values are always - * stored as strings; this function converts the string to a boolean - * for you. - * <p> - * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not a boolean. - * - * @return The setting's current value. - */ - default boolean getBool(String name) - throws Settings.SettingNotFoundException { - return getInt(name) != 0; - } - - /** - * Convenience function for updating a single settings value as a - * boolean. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putBool(String name, boolean value) { - return putInt(name, value ? 1 : 0); - } - - /** - * Convenience function for retrieving a single secure settings value - * as a {@code long}. Note that internally setting values are always - * stored as strings; this function converts the string to a {@code long} - * for you. The default value will be returned if the setting is - * not defined or not a {@code long}. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid {@code long}. - */ - default long getLong(String name, long def) { - String valString = getString(name); - return parseLongOrUseDefault(valString, def); - } - - /** Convert a string to a long, or uses a default if the string is malformed or null */ - static long parseLongOrUseDefault(String valString, long def) { - long value; - try { - value = valString != null ? Long.parseLong(valString) : def; - } catch (NumberFormatException e) { - value = def; - } - return value; - } - - /** - * Convenience function for retrieving a single secure settings value - * as a {@code long}. Note that internally setting values are always - * stored as strings; this function converts the string to a {@code long} - * for you. - * <p> - * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @return The setting's current value. - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not an integer. - */ - default long getLong(String name) - throws Settings.SettingNotFoundException { - String valString = getString(name); - return parseLongOrThrow(name, valString); - } - - /** Convert a string to a long, or throws an exception if the string is malformed or null */ - static long parseLongOrThrow(String name, String valString) - throws Settings.SettingNotFoundException { - try { - return Long.parseLong(valString); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** - * Convenience function for updating a secure settings value as a long - * integer. This will either create a new entry in the table if the - * given name does not exist, or modify the value of the existing row - * with that name. Note that internally setting values are always - * stored as strings, so this function converts the given value to a - * string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putLong(String name, long value) { - return putString(name, Long.toString(value)); - } - - /** - * Convenience function for retrieving a single secure settings value - * as a floating point number. Note that internally setting values are - * always stored as strings; this function converts the string to an - * float for you. The default value will be returned if the setting - * is not defined or not a valid float. - * - * @param name The name of the setting to retrieve. - * @param def Value to return if the setting is not defined. - * - * @return The setting's current value, or 'def' if it is not defined - * or not a valid float. - */ - default float getFloat(String name, float def) { - String v = getString(name); - return parseFloat(v, def); - } - - /** Convert a string to a float, or uses a default if the string is malformed or null */ - static float parseFloat(String v, float def) { - try { - return v != null ? Float.parseFloat(v) : def; - } catch (NumberFormatException e) { - return def; - } - } - - /** - * Convenience function for retrieving a single secure settings value - * as a float. Note that internally setting values are always - * stored as strings; this function converts the string to a float - * for you. - * <p> - * This version does not take a default value. If the setting has not - * been set, or the string value is not a number, - * it throws {@link Settings.SettingNotFoundException}. - * - * @param name The name of the setting to retrieve. - * - * @throws Settings.SettingNotFoundException Thrown if a setting by the given - * name can't be found or the setting value is not a float. - * - * @return The setting's current value. - */ - default float getFloat(String name) - throws Settings.SettingNotFoundException { - String v = getString(name); - return parseFloatOrThrow(name, v); - } - - /** Convert a string to a float, or throws an exception if the string is malformed or null */ - static float parseFloatOrThrow(String name, String v) - throws Settings.SettingNotFoundException { - if (v == null) { - throw new Settings.SettingNotFoundException(name); - } - try { - return Float.parseFloat(v); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - /** - * Convenience function for updating a single settings value as a - * floating point number. This will either create a new entry in the - * table if the given name does not exist, or modify the value of the - * existing row with that name. Note that internally setting values - * are always stored as strings, so this function converts the given - * value to a string before storing it. - * - * @param name The name of the setting to modify. - * @param value The new value for the setting. - * @return true if the value was set, false on database errors - */ - default boolean putFloat(String name, float value) { - return putString(name, Float.toString(value)); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt new file mode 100644 index 000000000000..ec89610ce014 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt @@ -0,0 +1,385 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.util.settings + +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.provider.Settings.SettingNotFoundException + +/** + * Used to interact with mainly with Settings.Global, but can also be used for Settings.System and + * Settings.Secure. To use the per-user System and Secure settings, [UserSettingsProxy] must be used + * instead. + * + * This interface can be implemented to give instance method (instead of static method) versions of + * Settings.Global. It can be injected into class constructors and then faked or mocked as needed in + * tests. + * + * You can ask for [GlobalSettings] to be injected as needed. + * + * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver] + * instances, unifying setting related actions in one place. + */ +interface SettingsProxy { + /** Returns the [ContentResolver] this instance was constructed with. */ + fun getContentResolver(): ContentResolver + + /** + * Construct the content URI for a particular name/value pair, useful for monitoring changes + * with a ContentObserver. + * + * @param name to look up in the table + * @return the corresponding content URI, or null if not present + */ + fun getUriFor(name: String): Uri + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * Implicitly calls [getUriFor] on the passed in name. + */ + fun registerContentObserver(name: String, settingsObserver: ContentObserver) { + registerContentObserver(getUriFor(name), settingsObserver) + } + + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ + fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) = + registerContentObserver(uri, false, settingsObserver) + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * Implicitly calls [getUriFor] on the passed in name. + */ + fun registerContentObserver( + name: String, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) = registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver) + + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ + fun registerContentObserver( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) = getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver) + + /** See [ContentResolver.unregisterContentObserver]. */ + fun unregisterContentObserver(settingsObserver: ContentObserver) = + getContentResolver().unregisterContentObserver(settingsObserver) + + /** + * Look up a name in the database. + * + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + fun getString(name: String): String + + /** + * Store a name/value pair into the database. + * + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + fun putString(name: String, value: String): Boolean + + /** + * Store a name/value pair into the database. + * + * The method takes an optional tag to associate with the setting which can be used to clear + * only settings made by your package and associated with this tag by passing the tag to + * [ ][.resetToDefaults]. Anyone can override the current tag. Also if another package changes + * the setting then the tag will be set to the one specified in the set call which can be null. + * Also any of the settings setters that do not take a tag as an argument effectively clears the + * tag. + * + * For example, if you set settings A and B with tags T1 and T2 and another app changes setting + * A (potentially to the same value), it can assign to it a tag T3 (note that now the package + * that changed the setting is not yours). Now if you reset your changes for T1 and T2 only + * setting B will be reset and A not (as it was changed by another package) but since A did not + * change you are in the desired initial state. Now if the other app changes the value of A + * (assuming you registered an observer in the beginning) you would detect that the setting was + * changed by another app and handle this appropriately (ignore, set back to some value, etc). + * + * Also the method takes an argument whether to make the value the default for this setting. If + * the system already specified a default value, then the one passed in here will **not** be set + * as the default. + * + * @param name to store. + * @param value to associate with the name. + * @param tag to associate with the setting. + * @param makeDefault whether to make the value the default one. + * @return true if the value was set, false on database errors. + * @see .resetToDefaults + */ + fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean + + /** + * Convenience function for retrieving a single secure settings value as an integer. Note that + * internally setting values are always stored as strings; this function converts the string to + * an integer for you. The default value will be returned if the setting is not defined or not + * an integer. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined or not a valid integer. + */ + fun getInt(name: String, def: Int): Int { + val v = getString(name) + return try { + v.toInt() + } catch (e: NumberFormatException) { + def + } + } + + /** + * Convenience function for retrieving a single secure settings value as an integer. Note that + * internally setting values are always stored as strings; this function converts the string to + * an integer for you. + * + * This version does not take a default value. If the setting has not been set, or the string + * value is not a number, it throws [Settings.SettingNotFoundException]. + * + * @param name The name of the setting to retrieve. + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be + * found or the setting value is not an integer. + */ + @Throws(SettingNotFoundException::class) + fun getInt(name: String): Int { + val v = getString(name) + return try { + v.toInt() + } catch (e: NumberFormatException) { + throw SettingNotFoundException(name) + } + } + + /** + * Convenience function for updating a single settings value as an integer. This will either + * create a new entry in the table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values are always stored as + * strings, so this function converts the given value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + fun putInt(name: String, value: Int): Boolean { + return putString(name, value.toString()) + } + + /** + * Convenience function for retrieving a single secure settings value as a boolean. Note that + * internally setting values are always stored as strings; this function converts the string to + * a boolean for you. The default value will be returned if the setting is not defined or not a + * boolean. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined or not a valid boolean. + */ + fun getBool(name: String, def: Boolean): Boolean { + return getInt(name, if (def) 1 else 0) != 0 + } + + /** + * Convenience function for retrieving a single secure settings value as a boolean. Note that + * internally setting values are always stored as strings; this function converts the string to + * a boolean for you. + * + * This version does not take a default value. If the setting has not been set, or the string + * value is not a number, it throws [Settings.SettingNotFoundException]. + * + * @param name The name of the setting to retrieve. + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be + * found or the setting value is not a boolean. + */ + @Throws(SettingNotFoundException::class) + fun getBool(name: String): Boolean { + return getInt(name) != 0 + } + + /** + * Convenience function for updating a single settings value as a boolean. This will either + * create a new entry in the table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values are always stored as + * strings, so this function converts the given value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + fun putBool(name: String, value: Boolean): Boolean { + return putInt(name, if (value) 1 else 0) + } + + /** + * Convenience function for retrieving a single secure settings value as a `long`. Note that + * internally setting values are always stored as strings; this function converts the string to + * a `long` for you. The default value will be returned if the setting is not defined or not a + * `long`. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined or not a valid `long`. + */ + fun getLong(name: String, def: Long): Long { + val valString = getString(name) + return parseLongOrUseDefault(valString, def) + } + + /** + * Convenience function for retrieving a single secure settings value as a `long`. Note that + * internally setting values are always stored as strings; this function converts the string to + * a `long` for you. + * + * This version does not take a default value. If the setting has not been set, or the string + * value is not a number, it throws [Settings.SettingNotFoundException]. + * + * @param name The name of the setting to retrieve. + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be + * found or the setting value is not an integer. + */ + @Throws(SettingNotFoundException::class) + fun getLong(name: String): Long { + val valString = getString(name) + return parseLongOrThrow(name, valString) + } + + /** + * Convenience function for updating a secure settings value as a long integer. This will either + * create a new entry in the table if the given name does not exist, or modify the value of the + * existing row with that name. Note that internally setting values are always stored as + * strings, so this function converts the given value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + fun putLong(name: String, value: Long): Boolean { + return putString(name, value.toString()) + } + + /** + * Convenience function for retrieving a single secure settings value as a floating point + * number. Note that internally setting values are always stored as strings; this function + * converts the string to an float for you. The default value will be returned if the setting is + * not defined or not a valid float. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined or not a valid float. + */ + fun getFloat(name: String, def: Float): Float { + val v = getString(name) + return parseFloat(v, def) + } + + /** + * Convenience function for retrieving a single secure settings value as a float. Note that + * internally setting values are always stored as strings; this function converts the string to + * a float for you. + * + * This version does not take a default value. If the setting has not been set, or the string + * value is not a number, it throws [Settings.SettingNotFoundException]. + * + * @param name The name of the setting to retrieve. + * @return The setting's current value. + * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be + * found or the setting value is not a float. + */ + @Throws(SettingNotFoundException::class) + fun getFloat(name: String): Float { + val v = getString(name) + return parseFloatOrThrow(name, v) + } + + /** + * Convenience function for updating a single settings value as a floating point number. This + * will either create a new entry in the table if the given name does not exist, or modify the + * value of the existing row with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a string before storing it. + * + * @param name The name of the setting to modify. + * @param value The new value for the setting. + * @return true if the value was set, false on database errors + */ + fun putFloat(name: String, value: Float): Boolean { + return putString(name, value.toString()) + } + + companion object { + /** Convert a string to a long, or uses a default if the string is malformed or null */ + @JvmStatic + fun parseLongOrUseDefault(valString: String, def: Long): Long { + val value: Long + value = + try { + valString.toLong() + } catch (e: NumberFormatException) { + def + } + return value + } + + /** Convert a string to a long, or throws an exception if the string is malformed or null */ + @JvmStatic + @Throws(SettingNotFoundException::class) + fun parseLongOrThrow(name: String, valString: String?): Long { + if (valString == null) { + throw SettingNotFoundException(name) + } + return try { + valString.toLong() + } catch (e: NumberFormatException) { + throw SettingNotFoundException(name) + } + } + + /** Convert a string to a float, or uses a default if the string is malformed or null */ + @JvmStatic + fun parseFloat(v: String?, def: Float): Float { + return try { + v?.toFloat() ?: def + } catch (e: NumberFormatException) { + def + } + } + + /** + * Convert a string to a float, or throws an exception if the string is malformed or null + */ + @JvmStatic + @Throws(SettingNotFoundException::class) + fun parseFloatOrThrow(name: String, v: String?): Float { + if (v == null) { + throw SettingNotFoundException(name) + } + return try { + v.toFloat() + } catch (e: NumberFormatException) { + throw SettingNotFoundException(name) + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java deleted file mode 100644 index 10cf08221fb3..000000000000 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.java +++ /dev/null @@ -1,283 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.util.settings; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.UserIdInt; -import android.content.ContentResolver; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.UserHandle; -import android.provider.Settings; - -import com.android.app.tracing.TraceUtils; -import com.android.systemui.settings.UserTracker; - -import kotlin.Unit; - -/** - * Used to interact with per-user Settings.Secure and Settings.System settings (but not - * Settings.Global, since those do not vary per-user) - * <p> - * This interface can be implemented to give instance method (instead of static method) versions - * of Settings.Secure and Settings.System. It can be injected into class constructors and then - * faked or mocked as needed in tests. - * <p> - * You can ask for {@link SecureSettings} or {@link SystemSettings} to be injected as needed. - * <p> - * This class also provides {@link #registerContentObserver(String, ContentObserver)} methods, - * normally found on {@link ContentResolver} instances, unifying setting related actions in one - * place. - */ -public interface UserSettingsProxy extends SettingsProxy { - - /** - * Returns that {@link UserTracker} this instance was constructed with. - */ - UserTracker getUserTracker(); - - /** - * Returns the user id for the associated {@link ContentResolver}. - */ - default int getUserId() { - return getContentResolver().getUserId(); - } - - /** - * Returns the actual current user handle when querying with the current user. Otherwise, - * returns the passed in user id. - */ - default int getRealUserHandle(int userHandle) { - if (userHandle != UserHandle.USER_CURRENT) { - return userHandle; - } - return getUserTracker().getUserId(); - } - - @Override - default void registerContentObserver(Uri uri, ContentObserver settingsObserver) { - registerContentObserverForUser(uri, settingsObserver, getUserId()); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver)}.' - */ - @Override - default void registerContentObserver(Uri uri, boolean notifyForDescendants, - ContentObserver settingsObserver) { - registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, getUserId()); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - getUriFor(name), settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, ContentObserver settingsObserver, int userHandle) { - registerContentObserverForUser( - uri, false, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - * - * Implicitly calls {@link #getUriFor(String)} on the passed in name. - */ - default void registerContentObserverForUser( - String name, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { - registerContentObserverForUser( - getUriFor(name), notifyForDescendants, settingsObserver, userHandle); - } - - /** - * Convenience wrapper around - * {@link ContentResolver#registerContentObserver(Uri, boolean, ContentObserver, int)} - */ - default void registerContentObserverForUser( - Uri uri, boolean notifyForDescendants, ContentObserver settingsObserver, - int userHandle) { - TraceUtils.trace( - () -> { - // The limit for trace tags length is 127 chars, which leaves us 90 for Uri. - return "USP#registerObserver#[" + uri.toString() + "]"; - }, () -> { - getContentResolver().registerContentObserver( - uri, notifyForDescendants, settingsObserver, - getRealUserHandle(userHandle)); - return Unit.INSTANCE; - }); - } - - /** - * Look up a name in the database. - * @param name to look up in the table - * @return the corresponding value, or null if not present - */ - @Override - default String getString(String name) { - return getStringForUser(name, getUserId()); - } - - /**See {@link #getString(String)}. */ - String getStringForUser(String name, int userHandle); - - /** - * Store a name/value pair into the database. Values written by this method will be - * overridden if a restore happens in the future. - * - * @param name to store - * @param value to associate with the name - * @return true if the value was set, false on database errors - */ - boolean putString(String name, String value, boolean overrideableByRestore); - - @Override - default boolean putString(String name, String value) { - return putStringForUser(name, value, getUserId()); - } - - /** See {@link #putString(String, String)}. */ - boolean putStringForUser(String name, String value, int userHandle); - - /** See {@link #putString(String, String)}. */ - boolean putStringForUser(@NonNull String name, @Nullable String value, @Nullable String tag, - boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore); - - @Override - default int getInt(String name, int def) { - return getIntForUser(name, def, getUserId()); - } - - /** See {@link #getInt(String, int)}. */ - default int getIntForUser(String name, int def, int userHandle) { - String v = getStringForUser(name, userHandle); - try { - return v != null ? Integer.parseInt(v) : def; - } catch (NumberFormatException e) { - return def; - } - } - - @Override - default int getInt(String name) throws Settings.SettingNotFoundException { - return getIntForUser(name, getUserId()); - } - - /** See {@link #getInt(String)}. */ - default int getIntForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); - try { - return Integer.parseInt(v); - } catch (NumberFormatException e) { - throw new Settings.SettingNotFoundException(name); - } - } - - @Override - default boolean putInt(String name, int value) { - return putIntForUser(name, value, getUserId()); - } - - /** See {@link #putInt(String, int)}. */ - default boolean putIntForUser(String name, int value, int userHandle) { - return putStringForUser(name, Integer.toString(value), userHandle); - } - - @Override - default boolean getBool(String name, boolean def) { - return getBoolForUser(name, def, getUserId()); - } - - /** See {@link #getBool(String, boolean)}. */ - default boolean getBoolForUser(String name, boolean def, int userHandle) { - return getIntForUser(name, def ? 1 : 0, userHandle) != 0; - } - - @Override - default boolean getBool(String name) throws Settings.SettingNotFoundException { - return getBoolForUser(name, getUserId()); - } - - /** See {@link #getBool(String)}. */ - default boolean getBoolForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - return getIntForUser(name, userHandle) != 0; - } - - @Override - default boolean putBool(String name, boolean value) { - return putBoolForUser(name, value, getUserId()); - } - - /** See {@link #putBool(String, boolean)}. */ - default boolean putBoolForUser(String name, boolean value, int userHandle) { - return putIntForUser(name, value ? 1 : 0, userHandle); - } - - /** See {@link #getLong(String, long)}. */ - default long getLongForUser(String name, long def, int userHandle) { - String valString = getStringForUser(name, userHandle); - return SettingsProxy.parseLongOrUseDefault(valString, def); - } - - /** See {@link #getLong(String)}. */ - default long getLongForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String valString = getStringForUser(name, userHandle); - return SettingsProxy.parseLongOrThrow(name, valString); - } - - /** See {@link #putLong(String, long)}. */ - default boolean putLongForUser(String name, long value, int userHandle) { - return putStringForUser(name, Long.toString(value), userHandle); - } - - /** See {@link #getFloat(String)}. */ - default float getFloatForUser(String name, float def, int userHandle) { - String v = getStringForUser(name, userHandle); - return SettingsProxy.parseFloat(v, def); - } - - /** See {@link #getFloat(String, float)}. */ - default float getFloatForUser(String name, int userHandle) - throws Settings.SettingNotFoundException { - String v = getStringForUser(name, userHandle); - return SettingsProxy.parseFloatOrThrow(name, v); - } - - /** See {@link #putFloat(String, float)} */ - default boolean putFloatForUser(String name, float value, int userHandle) { - return putStringForUser(name, Float.toString(value), userHandle); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt new file mode 100644 index 000000000000..2285270b0bc7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -0,0 +1,269 @@ +/* + * 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.util.settings + +import android.annotation.UserIdInt +import android.database.ContentObserver +import android.net.Uri +import android.os.UserHandle +import android.provider.Settings.SettingNotFoundException +import com.android.app.tracing.TraceUtils.trace +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat +import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow +import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow +import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault + +/** + * Used to interact with per-user Settings.Secure and Settings.System settings (but not + * Settings.Global, since those do not vary per-user) + * + * This interface can be implemented to give instance method (instead of static method) versions of + * Settings.Secure and Settings.System. It can be injected into class constructors and then faked or + * mocked as needed in tests. + * + * You can ask for [SecureSettings] or [SystemSettings] to be injected as needed. + * + * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver] + * instances, unifying setting related actions in one place. + */ +interface UserSettingsProxy : SettingsProxy { + + /** Returns that [UserTracker] this instance was constructed with. */ + val userTracker: UserTracker + + /** Returns the user id for the associated [ContentResolver]. */ + var userId: Int + get() = getContentResolver().userId + set(_) { + throw UnsupportedOperationException( + "userId cannot be set in interface, use setter from an implementation instead." + ) + } + + /** + * Returns the actual current user handle when querying with the current user. Otherwise, + * returns the passed in user id. + */ + fun getRealUserHandle(userHandle: Int): Int { + return if (userHandle != UserHandle.USER_CURRENT) { + userHandle + } else userTracker.userId + } + + override fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { + registerContentObserverForUser(uri, settingsObserver, userId) + } + + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ + override fun registerContentObserver( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) { + registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, userId) + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver] + * + * Implicitly calls [getUriFor] on the passed in name. + */ + fun registerContentObserverForUser( + name: String, + settingsObserver: ContentObserver, + userHandle: Int + ) { + registerContentObserverForUser(getUriFor(name), settingsObserver, userHandle) + } + + /** Convenience wrapper around [ContentResolver.registerContentObserver] */ + fun registerContentObserverForUser( + uri: Uri, + settingsObserver: ContentObserver, + userHandle: Int + ) { + registerContentObserverForUser(uri, false, settingsObserver, userHandle) + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver] + * + * Implicitly calls [getUriFor] on the passed in name. + */ + fun registerContentObserverForUser( + name: String, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver, + userHandle: Int + ) { + registerContentObserverForUser( + getUriFor(name), + notifyForDescendants, + settingsObserver, + userHandle + ) + } + + /** Convenience wrapper around [ContentResolver.registerContentObserver] */ + fun registerContentObserverForUser( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver, + userHandle: Int + ) { + trace({ "USP#registerObserver#[$uri]" }) { + getContentResolver() + .registerContentObserver( + uri, + notifyForDescendants, + settingsObserver, + getRealUserHandle(userHandle) + ) + Unit + } + } + + /** + * Look up a name in the database. + * + * @param name to look up in the table + * @return the corresponding value, or null if not present + */ + override fun getString(name: String): String { + return getStringForUser(name, userId) + } + + /** See [getString]. */ + fun getStringForUser(name: String, userHandle: Int): String + + /** + * Store a name/value pair into the database. Values written by this method will be overridden + * if a restore happens in the future. + * + * @param name to store + * @param value to associate with the name + * @return true if the value was set, false on database errors + */ + fun putString(name: String, value: String, overrideableByRestore: Boolean): Boolean + override fun putString(name: String, value: String): Boolean { + return putStringForUser(name, value, userId) + } + + /** Similar implementation to [putString] for the specified [userHandle]. */ + fun putStringForUser(name: String, value: String, userHandle: Int): Boolean + + /** Similar implementation to [putString] for the specified [userHandle]. */ + fun putStringForUser( + name: String, + value: String, + tag: String?, + makeDefault: Boolean, + @UserIdInt userHandle: Int, + overrideableByRestore: Boolean + ): Boolean + + override fun getInt(name: String, def: Int): Int { + return getIntForUser(name, def, userId) + } + + /** Similar implementation to [getInt] for the specified [userHandle]. */ + fun getIntForUser(name: String, def: Int, userHandle: Int): Int { + val v = getStringForUser(name, userHandle) + return try { + v.toInt() + } catch (e: NumberFormatException) { + def + } + } + + @Throws(SettingNotFoundException::class) + override fun getInt(name: String) = getIntForUser(name, userId) + + /** Similar implementation to [getInt] for the specified [userHandle]. */ + @Throws(SettingNotFoundException::class) + fun getIntForUser(name: String, userHandle: Int): Int { + val v = getStringForUser(name, userHandle) + return try { + v.toInt() + } catch (e: NumberFormatException) { + throw SettingNotFoundException(name) + } + } + + override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId) + + /** Similar implementation to [getInt] for the specified [userHandle]. */ + fun putIntForUser(name: String, value: Int, userHandle: Int) = + putStringForUser(name, value.toString(), userHandle) + + override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId) + + /** Similar implementation to [getBool] for the specified [userHandle]. */ + fun getBoolForUser(name: String, def: Boolean, userHandle: Int) = + getIntForUser(name, if (def) 1 else 0, userHandle) != 0 + + @Throws(SettingNotFoundException::class) + override fun getBool(name: String) = getBoolForUser(name, userId) + + /** Similar implementation to [getBool] for the specified [userHandle]. */ + @Throws(SettingNotFoundException::class) + fun getBoolForUser(name: String, userHandle: Int): Boolean { + return getIntForUser(name, userHandle) != 0 + } + + override fun putBool(name: String, value: Boolean): Boolean { + return putBoolForUser(name, value, userId) + } + + /** Similar implementation to [putBool] for the specified [userHandle]. */ + fun putBoolForUser(name: String, value: Boolean, userHandle: Int) = + putIntForUser(name, if (value) 1 else 0, userHandle) + + /** Similar implementation to [getLong] for the specified [userHandle]. */ + fun getLongForUser(name: String, def: Long, userHandle: Int): Long { + val valString = getStringForUser(name, userHandle) + return parseLongOrUseDefault(valString, def) + } + + /** Similar implementation to [getLong] for the specified [userHandle]. */ + @Throws(SettingNotFoundException::class) + fun getLongForUser(name: String, userHandle: Int): Long { + val valString = getStringForUser(name, userHandle) + return parseLongOrThrow(name, valString) + } + + /** Similar implementation to [putLong] for the specified [userHandle]. */ + fun putLongForUser(name: String, value: Long, userHandle: Int) = + putStringForUser(name, value.toString(), userHandle) + + /** Similar implementation to [getFloat] for the specified [userHandle]. */ + fun getFloatForUser(name: String, def: Float, userHandle: Int): Float { + val v = getStringForUser(name, userHandle) + return parseFloat(v, def) + } + + /** Similar implementation to [getFloat] for the specified [userHandle]. */ + @Throws(SettingNotFoundException::class) + fun getFloatForUser(name: String, userHandle: Int): Float { + val v = getStringForUser(name, userHandle) + return parseFloatOrThrow(name, v) + } + + /** Similar implementation to [putFloat] for the specified [userHandle]. */ + fun putFloatForUser(name: String, value: Float, userHandle: Int) = + putStringForUser(name, value.toString(), userHandle) +} 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/dagger/MediaDevicesModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt index 155102c9b9a7..369610882959 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/MediaDevicesModule.kt @@ -27,6 +27,8 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactoryImpl +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaControllerInteractorImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -41,6 +43,11 @@ interface MediaDevicesModule { impl: LocalMediaRepositoryFactoryImpl ): LocalMediaRepositoryFactory + @Binds + fun bindMediaControllerInteractor( + impl: MediaControllerInteractorImpl + ): MediaControllerInteractor + companion object { @Provides diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt index 19d9c3f125b7..3eec3d91c809 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt @@ -32,7 +32,6 @@ import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope -import com.android.systemui.volume.panel.shared.model.filterData import javax.inject.Inject import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope @@ -69,14 +68,9 @@ constructor( communicationDevice?.toAudioOutputDevice() } } else { - mediaOutputInteractor.defaultActiveMediaSession - .filterData() - .flatMapLatest { - localMediaRepositoryFactory - .create(it?.packageName) - .currentConnectedDevice - } - .map { mediaDevice -> mediaDevice?.toAudioOutputDevice() } + mediaOutputInteractor.currentConnectedDevice.map { mediaDevice -> + mediaDevice?.toAudioOutputDevice() + } } } .map { it ?: AudioOutputDevice.Unknown } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt index 8ce3b1fa1e73..e1787e8b0a9e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/data/repository/AncSliceRepository.kt @@ -21,21 +21,15 @@ import android.net.Uri import androidx.slice.Slice import androidx.slice.SliceViewManager import com.android.settingslib.bluetooth.BluetoothUtils -import com.android.settingslib.media.BluetoothMediaDevice -import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.slice.sliceForUri -import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map /** Provides ANC slice data */ interface AncSliceRepository { @@ -48,34 +42,30 @@ interface AncSliceRepository { * - there is no supported device connected; * - there is no slice provider for the uri; */ - fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> + fun ancSlice( + device: BluetoothDevice, + width: Int, + isCollapsed: Boolean, + hideLabel: Boolean + ): Flow<Slice?> } -@OptIn(ExperimentalCoroutinesApi::class) class AncSliceRepositoryImpl @AssistedInject constructor( - mediaRepositoryFactory: LocalMediaRepositoryFactory, - @Background private val backgroundCoroutineContext: CoroutineContext, + @Main private val mainCoroutineContext: CoroutineContext, @Assisted private val sliceViewManager: SliceViewManager, ) : AncSliceRepository { - private val localMediaRepository = mediaRepositoryFactory.create(null) - - override fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> { - return localMediaRepository.currentConnectedDevice - .map { - (it as? BluetoothMediaDevice) - ?.cachedDevice - ?.device - ?.getExtraControlUri(width, isCollapsed, hideLabel) - } - .distinctUntilChanged() - .flatMapLatest { sliceUri -> - sliceUri ?: return@flatMapLatest flowOf(null) - sliceViewManager.sliceForUri(sliceUri) - } - .flowOn(backgroundCoroutineContext) + override fun ancSlice( + device: BluetoothDevice, + width: Int, + isCollapsed: Boolean, + hideLabel: Boolean + ): Flow<Slice?> { + val sliceUri = + device.getExtraControlUri(width, isCollapsed, hideLabel) ?: return flowOf(null) + return sliceViewManager.sliceForUri(sliceUri).flowOn(mainCoroutineContext) } private fun BluetoothDevice.getExtraControlUri( diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt index cefa26907710..cfff45736159 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/domain/interactor/AncSliceInteractor.kt @@ -19,6 +19,8 @@ package com.android.systemui.volume.panel.component.anc.domain.interactor import android.app.slice.Slice.HINT_ERROR import android.app.slice.SliceItem.FORMAT_SLICE import androidx.slice.Slice +import com.android.systemui.volume.domain.interactor.AudioOutputInteractor +import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.panel.component.anc.data.repository.AncSliceRepository import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope @@ -32,6 +34,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn /** Provides a valid slice from [AncSliceRepository]. */ @@ -40,6 +43,7 @@ import kotlinx.coroutines.flow.stateIn class AncSliceInteractor @Inject constructor( + private val audioOutputInteractor: AudioOutputInteractor, private val ancSliceRepository: AncSliceRepository, scope: CoroutineScope, ) { @@ -70,9 +74,20 @@ constructor( * remove the labels from the [Slice]. */ private fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> { - return ancSliceRepository - .ancSlice(width = width, isCollapsed = isCollapsed, hideLabel = hideLabel) - .filter { it?.isValidSlice() != false } + return audioOutputInteractor.currentAudioDevice.flatMapLatest { outputDevice -> + if (outputDevice is AudioOutputDevice.Bluetooth) { + ancSliceRepository + .ancSlice( + device = outputDevice.cachedBluetoothDevice.device, + width = width, + isCollapsed = isCollapsed, + hideLabel = hideLabel, + ) + .filter { it?.isValidSlice() != false } + } else { + flowOf(null) + } + } } private fun Slice.isValidSlice(): Boolean { diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt index bee79bb68141..c980eb43ec97 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/anc/ui/viewmodel/AncViewModel.kt @@ -16,7 +16,9 @@ package com.android.systemui.volume.panel.component.anc.ui.viewmodel +import android.content.Intent import androidx.slice.Slice +import androidx.slice.SliceItem import com.android.systemui.volume.panel.component.anc.domain.AncAvailabilityCriteria import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor import com.android.systemui.volume.panel.component.anc.domain.model.AncSlices @@ -59,6 +61,31 @@ constructor( .map { it.buttonSlice } .stateIn(coroutineScope, SharingStarted.Eagerly, null) + fun isClickable(slice: Slice?): Boolean { + slice ?: return false + val slices = ArrayDeque<SliceItem>() + slices.addAll(slice.items) + while (slices.isNotEmpty()) { + val item: SliceItem = slices.removeFirst() + when (item.format) { + android.app.slice.SliceItem.FORMAT_ACTION -> { + val itemActionIntent: Intent? = item.action?.intent + if (itemActionIntent?.hasExtra(EXTRA_ANC_ENABLED) == true) { + return itemActionIntent.getBooleanExtra(EXTRA_ANC_ENABLED, true) + } + } + android.app.slice.SliceItem.FORMAT_SLICE -> { + item.slice?.items?.let(slices::addAll) + } + } + } + return true + } + + private companion object { + const val EXTRA_ANC_ENABLED = "EXTRA_ANC_ENABLED" + } + /** Call this to update [popupSlice] width in a reaction to container size change. */ fun onPopupSliceWidthChanged(width: Int) { interactor.onPopupSliceWidthChanged(width) diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt index 1f037c0280e3..a714f8078db7 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/MediaControllerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.settingslib.volume.data.repository +package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor import android.media.MediaMetadata import android.media.session.MediaController @@ -22,79 +22,75 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import android.os.Bundle import android.os.Handler +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel +import javax.inject.Inject import kotlinx.coroutines.channels.ProducerScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.launch -/** [MediaController.Callback] flow representation. */ -fun MediaController.stateChanges(handler: Handler): Flow<MediaControllerChange> { - return callbackFlow { - val callback = MediaControllerCallbackProducer(this) - registerCallback(callback, handler) - awaitClose { unregisterCallback(callback) } - } -} - -/** Models particular change event received by [MediaController.Callback]. */ -sealed interface MediaControllerChange { - - data object SessionDestroyed : MediaControllerChange - - data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChange - - data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChange +interface MediaControllerInteractor { - data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChange - - data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) : - MediaControllerChange - - data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChange - - data class ExtrasChanged(val extras: Bundle?) : MediaControllerChange + /** [MediaController.Callback] flow representation. */ + fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> +} - data class AudioInfoChanged(val info: MediaController.PlaybackInfo?) : MediaControllerChange +@SysUISingleton +class MediaControllerInteractorImpl +@Inject +constructor( + @Background private val backgroundHandler: Handler, +) : MediaControllerInteractor { + + override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> { + return conflatedCallbackFlow { + val callback = MediaControllerCallbackProducer(this) + mediaController.registerCallback(callback, backgroundHandler) + awaitClose { mediaController.unregisterCallback(callback) } + } + } } private class MediaControllerCallbackProducer( - private val producingScope: ProducerScope<MediaControllerChange> + private val producingScope: ProducerScope<MediaControllerChangeModel> ) : MediaController.Callback() { override fun onSessionDestroyed() { - send(MediaControllerChange.SessionDestroyed) + send(MediaControllerChangeModel.SessionDestroyed) } override fun onSessionEvent(event: String, extras: Bundle?) { - send(MediaControllerChange.SessionEvent(event, extras)) + send(MediaControllerChangeModel.SessionEvent(event, extras)) } override fun onPlaybackStateChanged(state: PlaybackState?) { - send(MediaControllerChange.PlaybackStateChanged(state)) + send(MediaControllerChangeModel.PlaybackStateChanged(state)) } override fun onMetadataChanged(metadata: MediaMetadata?) { - send(MediaControllerChange.MetadataChanged(metadata)) + send(MediaControllerChangeModel.MetadataChanged(metadata)) } override fun onQueueChanged(queue: MutableList<MediaSession.QueueItem>?) { - send(MediaControllerChange.QueueChanged(queue)) + send(MediaControllerChangeModel.QueueChanged(queue)) } override fun onQueueTitleChanged(title: CharSequence?) { - send(MediaControllerChange.QueueTitleChanged(title)) + send(MediaControllerChangeModel.QueueTitleChanged(title)) } override fun onExtrasChanged(extras: Bundle?) { - send(MediaControllerChange.ExtrasChanged(extras)) + send(MediaControllerChangeModel.ExtrasChanged(extras)) } - override fun onAudioInfoChanged(info: MediaController.PlaybackInfo?) { - send(MediaControllerChange.AudioInfoChanged(info)) + override fun onAudioInfoChanged(info: MediaController.PlaybackInfo) { + send(MediaControllerChangeModel.AudioInfoChanged(info)) } - private fun send(change: MediaControllerChange) { + private fun send(change: MediaControllerChangeModel) { producingScope.launch { producingScope.send(change) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt index dc73344fafe6..6e1ebc820b08 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractor.kt @@ -18,11 +18,9 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.media.session.MediaController import android.media.session.PlaybackState -import android.os.Handler -import com.android.settingslib.volume.data.repository.MediaControllerChange import com.android.settingslib.volume.data.repository.MediaControllerRepository -import com.android.settingslib.volume.data.repository.stateChanges import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject @@ -45,38 +43,39 @@ class MediaDeviceSessionInteractor @Inject constructor( @Background private val backgroundCoroutineContext: CoroutineContext, - @Background private val backgroundHandler: Handler, + private val mediaControllerInteractor: MediaControllerInteractor, private val mediaControllerRepository: MediaControllerRepository, ) { /** [PlaybackState] changes for the [MediaDeviceSession]. */ fun playbackState(session: MediaDeviceSession): Flow<PlaybackState?> { return stateChanges(session) { - emit(MediaControllerChange.PlaybackStateChanged(it.playbackState)) + emit(MediaControllerChangeModel.PlaybackStateChanged(it.playbackState)) } - .filterIsInstance(MediaControllerChange.PlaybackStateChanged::class) + .filterIsInstance(MediaControllerChangeModel.PlaybackStateChanged::class) .map { it.state } } /** [MediaController.PlaybackInfo] changes for the [MediaDeviceSession]. */ - fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo?> { + fun playbackInfo(session: MediaDeviceSession): Flow<MediaController.PlaybackInfo> { return stateChanges(session) { - emit(MediaControllerChange.AudioInfoChanged(it.playbackInfo)) + emit(MediaControllerChangeModel.AudioInfoChanged(it.playbackInfo)) } - .filterIsInstance(MediaControllerChange.AudioInfoChanged::class) + .filterIsInstance(MediaControllerChangeModel.AudioInfoChanged::class) .map { it.info } } private fun stateChanges( session: MediaDeviceSession, - onStart: suspend FlowCollector<MediaControllerChange>.(controller: MediaController) -> Unit, - ): Flow<MediaControllerChange?> = + onStart: + suspend FlowCollector<MediaControllerChangeModel>.(controller: MediaController) -> Unit, + ): Flow<MediaControllerChangeModel?> = mediaControllerRepository.activeSessions .flatMapLatest { controllers -> val controller: MediaController = findControllerForSession(controllers, session) ?: return@flatMapLatest flowOf(null) - controller.stateChanges(backgroundHandler).onStart { onStart(controller) } + mediaControllerInteractor.stateChanges(controller).onStart { onStart(controller) } } .flowOn(backgroundCoroutineContext) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt index 22c053099ac5..199bc3b78dd2 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt @@ -33,15 +33,15 @@ constructor( private val mediaOutputDialogManager: MediaOutputDialogManager, ) { - fun onBarClick(sessionWithPlaybackState: SessionWithPlaybackState?, expandable: Expandable) { + fun onBarClick(sessionWithPlaybackState: SessionWithPlaybackState?, expandable: Expandable?) { if (sessionWithPlaybackState?.isPlaybackActive == true) { mediaOutputDialogManager.createAndShowWithController( sessionWithPlaybackState.session.packageName, false, - expandable.dialogController() + expandable?.dialogController() ) } else { - mediaOutputDialogManager.createAndShowForSystemRouting(expandable.dialogController()) + mediaOutputDialogManager.createAndShowForSystemRouting(expandable?.dialogController()) } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt index b974f90191e9..9fbd79accf80 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt @@ -36,14 +36,15 @@ import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -58,21 +59,31 @@ constructor( @VolumePanelScope private val coroutineScope: CoroutineScope, @Background private val backgroundCoroutineContext: CoroutineContext, mediaControllerRepository: MediaControllerRepository, + private val mediaControllerInteractor: MediaControllerInteractor, ) { private val activeMediaControllers: Flow<MediaControllers> = mediaControllerRepository.activeSessions + .flatMapLatest { activeSessions -> + activeSessions + .map { activeSession -> activeSession.stateChanges() } + .merge() + .map { activeSessions } + .onStart { emit(activeSessions) } + } .map { getMediaControllers(it) } - .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1) + .stateIn(coroutineScope, SharingStarted.Eagerly, MediaControllers(null, null)) /** [MediaDeviceSessions] that contains currently active sessions. */ val activeMediaDeviceSessions: Flow<MediaDeviceSessions> = - activeMediaControllers.map { - MediaDeviceSessions( - local = it.local?.mediaDeviceSession(), - remote = it.remote?.mediaDeviceSession() - ) - } + activeMediaControllers + .map { + MediaDeviceSessions( + local = it.local?.mediaDeviceSession(), + remote = it.remote?.mediaDeviceSession() + ) + } + .stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null)) /** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */ val defaultActiveMediaSession: StateFlow<Result<MediaDeviceSession?>> = @@ -89,13 +100,17 @@ constructor( .flowOn(backgroundCoroutineContext) .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading()) - private val localMediaRepository: SharedFlow<LocalMediaRepository> = + private val localMediaRepository: Flow<LocalMediaRepository> = defaultActiveMediaSession .filterData() .map { it?.packageName } .distinctUntilChanged() .map { localMediaRepositoryFactory.create(it) } - .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 1) + .stateIn( + coroutineScope, + SharingStarted.Eagerly, + localMediaRepositoryFactory.create(null) + ) /** Currently connected [MediaDevice]. */ val currentConnectedDevice: Flow<MediaDevice?> = @@ -134,21 +149,33 @@ constructor( } if (!remoteMediaSessions.contains(controller.packageName)) { remoteMediaSessions.add(controller.packageName) - if (remoteController == null) { - remoteController = controller - } + remoteController = chooseController(remoteController, controller) } } MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> { if (controller.packageName in remoteMediaSessions) continue - if (localController != null) continue - localController = controller + localController = chooseController(localController, controller) } } } return MediaControllers(local = localController, remote = remoteController) } + private fun chooseController( + currentController: MediaController?, + newController: MediaController, + ): MediaController { + if (currentController == null) { + return newController + } + val isNewControllerActive = newController.playbackState?.isActive == true + val isCurrentControllerActive = currentController.playbackState?.isActive == true + if (isNewControllerActive && !isCurrentControllerActive) { + return newController + } + return currentController + } + private suspend fun MediaController.mediaDeviceSession(): MediaDeviceSession? { return MediaDeviceSession( packageName = packageName, @@ -160,6 +187,17 @@ constructor( ) } + private fun MediaController?.stateChanges(): Flow<MediaController?> { + if (this == null) { + return flowOf(null) + } + + return mediaControllerInteractor + .stateChanges(this) + .map { this } + .onStart { emit(this@stateChanges) } + } + private data class MediaControllers( val local: MediaController?, val remote: MediaController?, diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt new file mode 100644 index 000000000000..8b5116a64365 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaControllerChangeModel.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.mediaoutput.domain.model + +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession +import android.media.session.PlaybackState +import android.os.Bundle + +/** Models particular change event received by [MediaController.Callback]. */ +sealed interface MediaControllerChangeModel { + + data object SessionDestroyed : MediaControllerChangeModel + + data class SessionEvent(val event: String, val extras: Bundle?) : MediaControllerChangeModel + + data class PlaybackStateChanged(val state: PlaybackState?) : MediaControllerChangeModel + + data class MetadataChanged(val metadata: MediaMetadata?) : MediaControllerChangeModel + + data class QueueChanged(val queue: MutableList<MediaSession.QueueItem>?) : + MediaControllerChangeModel + + data class QueueTitleChanged(val title: CharSequence?) : MediaControllerChangeModel + + data class ExtrasChanged(val extras: Bundle?) : MediaControllerChangeModel + + data class AudioInfoChanged(val info: MediaController.PlaybackInfo) : + MediaControllerChangeModel +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt index 192e0ec76132..be3a529d9a75 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt @@ -143,7 +143,7 @@ constructor( null, ) - fun onBarClick(expandable: Expandable) { + fun onBarClick(expandable: Expandable?) { uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED) val result = sessionWithPlaybackState.value actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable) diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt index 298ca67d92a8..9ca50d6f136b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt @@ -18,11 +18,9 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo -import com.android.settingslib.media.BluetoothMediaDevice -import com.android.settingslib.media.MediaDevice -import com.android.settingslib.media.PhoneMediaDevice import com.android.settingslib.media.domain.interactor.SpatializerInteractor -import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.domain.interactor.AudioOutputInteractor +import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope @@ -46,16 +44,20 @@ import kotlinx.coroutines.flow.stateIn class SpatialAudioComponentInteractor @Inject constructor( - mediaOutputInteractor: MediaOutputInteractor, + audioOutputInteractor: AudioOutputInteractor, private val spatializerInteractor: SpatializerInteractor, @VolumePanelScope private val coroutineScope: CoroutineScope, ) { private val changes = MutableSharedFlow<Unit>() private val currentAudioDeviceAttributes: StateFlow<AudioDeviceAttributes?> = - mediaOutputInteractor.currentConnectedDevice - .map { mediaDevice -> - if (mediaDevice == null) builtinSpeaker else mediaDevice.getAudioDeviceAttributes() + audioOutputInteractor.currentAudioDevice + .map { audioDevice -> + if (audioDevice is AudioOutputDevice.Unknown) { + builtinSpeaker + } else { + audioDevice.getAudioDeviceAttributes() + } } .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), builtinSpeaker) @@ -135,36 +137,35 @@ constructor( changes.emit(Unit) } - private suspend fun MediaDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? { + private suspend fun AudioOutputDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? { when (this) { - is PhoneMediaDevice -> return builtinSpeaker - is BluetoothMediaDevice -> { - val device = cachedDevice ?: return null + is AudioOutputDevice.BuiltIn -> return builtinSpeaker + is AudioOutputDevice.Bluetooth -> { return listOf( AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLE_HEADSET, - device.address, + cachedBluetoothDevice.address, ), AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLE_SPEAKER, - device.address, + cachedBluetoothDevice.address, ), AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLE_BROADCAST, - device.address, + cachedBluetoothDevice.address, ), AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - device.address, + cachedBluetoothDevice.address, ), AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HEARING_AID, - device.address, + cachedBluetoothDevice.address, ) ) .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) } 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/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.kt new file mode 100644 index 000000000000..19b9ead88ebb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/SlidersExpandableViewModel.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.ui.viewmodel + +/** + * Models expandability state of the + * [com.android.systemui.volume.panel.component.volume.ui.composable.VolumeSlidersComponent]. + */ +sealed interface SlidersExpandableViewModel { + + /** [SlidersExpandableViewModel] is not loaded. */ + data object Unavailable : SlidersExpandableViewModel + + data class Expandable(val isExpanded: Boolean) : SlidersExpandableViewModel + + data object Fixed : SlidersExpandableViewModel +} diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java index 1568e8c011a1..2e29bbd33f92 100644 --- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java @@ -20,6 +20,7 @@ import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; import static android.app.WallpaperManager.SetWallpaperFlags; +import static com.android.systemui.Flags.fixImageWallpaperCrashSurfaceAlreadyReleased; import static com.android.window.flags.Flags.offloadColorExtraction; import android.annotation.Nullable; @@ -128,8 +129,17 @@ public class ImageWallpaper extends WallpaperService { * and if the count is 0, unload the bitmap */ private int mBitmapUsages = 0; + + /** + * Main lock for long operations (loading the bitmap or processing colors). + */ private final Object mLock = new Object(); + /** + * Lock for SurfaceHolder operations. Should only be acquired after the main lock. + */ + private final Object mSurfaceLock = new Object(); + CanvasEngine() { super(); setFixedSizeAllowed(true); @@ -223,6 +233,12 @@ public class ImageWallpaper extends WallpaperService { if (DEBUG) { Log.i(TAG, "onSurfaceDestroyed"); } + if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { + synchronized (mSurfaceLock) { + mSurfaceHolder = null; + } + return; + } mLongExecutor.execute(this::onSurfaceDestroyedSynchronized); } @@ -259,7 +275,7 @@ public class ImageWallpaper extends WallpaperService { } private void drawFrameInternal() { - if (mSurfaceHolder == null) { + if (mSurfaceHolder == null && !fixImageWallpaperCrashSurfaceAlreadyReleased()) { Log.i(TAG, "attempt to draw a frame without a valid surface"); return; } @@ -268,6 +284,19 @@ public class ImageWallpaper extends WallpaperService { if (!isBitmapLoaded()) { loadWallpaperAndDrawFrameInternal(); } else { + if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { + synchronized (mSurfaceLock) { + if (mSurfaceHolder == null) { + Log.i(TAG, "Surface released before the image could be drawn"); + return; + } + mBitmapUsages++; + drawFrameOnCanvas(mBitmap); + reportEngineShown(false); + unloadBitmapIfNotUsedInternal(); + return; + } + } mBitmapUsages++; drawFrameOnCanvas(mBitmap); reportEngineShown(false); @@ -328,9 +357,14 @@ public class ImageWallpaper extends WallpaperService { mBitmap.recycle(); } mBitmap = null; - - final Surface surface = getSurfaceHolder().getSurface(); - surface.hwuiDestroy(); + if (fixImageWallpaperCrashSurfaceAlreadyReleased()) { + synchronized (mSurfaceLock) { + if (mSurfaceHolder != null) mSurfaceHolder.getSurface().hwuiDestroy(); + } + } else { + final Surface surface = getSurfaceHolder().getSurface(); + surface.hwuiDestroy(); + } mWallpaperManager.forgetLoadedWallpaper(); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 263ddc175647..e073f7cde826 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -21,6 +21,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_B import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; @@ -97,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 @@ -273,6 +274,13 @@ public final class WMShell implements splitScreen.setSplitscreenFocus(leftOrTop); } }); + splitScreen.registerSplitAnimationListener(new SplitScreen.SplitInvocationListener() { + @Override + public void onSplitAnimationInvoked(boolean animationRunning) { + mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION, animationRunning) + .commitUpdate(mDisplayTracker.getDefaultDisplayId()); + } + }, mSysUiMainExecutor); } @VisibleForTesting diff --git a/packages/SystemUI/tests/goldens/animateFailure.json b/packages/SystemUI/tests/goldens/animateFailure.json new file mode 100644 index 000000000000..a008f922969b --- /dev/null +++ b/packages/SystemUI/tests/goldens/animateFailure.json @@ -0,0 +1,612 @@ +{ + "frame_ids": [ + "before", + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + 544, + 560, + 576, + 592, + 608, + 624, + 640, + 656, + 672, + 688, + 704, + 720, + 736, + 752, + "after" + ], + "features": [ + { + "name": "PinBouncer::dotScaling", + "type": "float[]", + "data_points": [ + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + [ + 0.93142855, + 1, + 1, + 0.93142855, + 1, + 1, + 0.93142855, + 1, + 1 + ], + [ + 0.86285716, + 1, + 1, + 0.86285716, + 1, + 1, + 0.86285716, + 1, + 1 + ], + [ + 0.7942857, + 0.93571424, + 1, + 0.7942857, + 0.93571424, + 1, + 0.7942857, + 0.93571424, + 1 + ], + [ + 0.78571427, + 0.86714286, + 1, + 0.78571427, + 0.86714286, + 1, + 0.78571427, + 0.86714286, + 1 + ], + [ + 0.78571427, + 0.7985714, + 0.94000006, + 0.78571427, + 0.7985714, + 0.94000006, + 0.78571427, + 0.7985714, + 0.94000006 + ], + [ + 0.7872844, + 0.78571427, + 0.87142855, + 0.7872844, + 0.78571427, + 0.87142855, + 0.7872844, + 0.78571427, + 0.87142855 + ], + [ + 0.7925441, + 0.78571427, + 0.8028571, + 0.7925441, + 0.78571427, + 0.8028571, + 0.7925441, + 0.78571427, + 0.8028571 + ], + [ + 0.8037901, + 0.7872844, + 0.78571427, + 0.8037901, + 0.7872844, + 0.78571427, + 0.8037901, + 0.7872844, + 0.78571427 + ], + [ + 0.8223549, + 0.7925441, + 0.78571427, + 0.8223549, + 0.7925441, + 0.78571427, + 0.8223549, + 0.7925441, + 0.78571427 + ], + [ + 0.84427696, + 0.8037901, + 0.7872844, + 0.84427696, + 0.8037901, + 0.7872844, + 0.84427696, + 0.8037901, + 0.7872844 + ], + [ + 0.864585, + 0.8223549, + 0.7925441, + 0.864585, + 0.8223549, + 0.7925441, + 0.864585, + 0.8223549, + 0.7925441 + ], + [ + 0.8817363, + 0.84427696, + 0.8037901, + 0.8817363, + 0.84427696, + 0.8037901, + 0.8817363, + 0.84427696, + 0.8037901 + ], + [ + 0.89635646, + 0.864585, + 0.8223549, + 0.89635646, + 0.864585, + 0.8223549, + 0.89635646, + 0.864585, + 0.8223549 + ], + [ + 0.908528, + 0.8817363, + 0.84427696, + 0.908528, + 0.8817363, + 0.84427696, + 0.908528, + 0.8817363, + 0.84427696 + ], + [ + 0.91881, + 0.89635646, + 0.864585, + 0.91881, + 0.89635646, + 0.864585, + 0.91881, + 0.89635646, + 0.864585 + ], + [ + 0.9280548, + 0.908528, + 0.8817363, + 0.9280548, + 0.908528, + 0.8817363, + 0.9280548, + 0.908528, + 0.8817363 + ], + [ + 0.9362979, + 0.91881, + 0.89635646, + 0.9362979, + 0.91881, + 0.89635646, + 0.9362979, + 0.91881, + 0.89635646 + ], + [ + 0.9433999, + 0.9280548, + 0.908528, + 0.9433999, + 0.9280548, + 0.908528, + 0.9433999, + 0.9280548, + 0.908528 + ], + [ + 0.9497632, + 0.9362979, + 0.91881, + 0.9497632, + 0.9362979, + 0.91881, + 0.9497632, + 0.9362979, + 0.91881 + ], + [ + 0.9552536, + 0.9433999, + 0.9280548, + 0.9552536, + 0.9433999, + 0.9280548, + 0.9552536, + 0.9433999, + 0.9280548 + ], + [ + 0.96035147, + 0.9497632, + 0.9362979, + 0.96035147, + 0.9497632, + 0.9362979, + 0.96035147, + 0.9497632, + 0.9362979 + ], + [ + 0.9649086, + 0.9552536, + 0.9433999, + 0.9649086, + 0.9552536, + 0.9433999, + 0.9649086, + 0.9552536, + 0.9433999 + ], + [ + 0.96897155, + 0.96035147, + 0.9497632, + 0.96897155, + 0.96035147, + 0.9497632, + 0.96897155, + 0.96035147, + 0.9497632 + ], + [ + 0.9727647, + 0.9649086, + 0.9552536, + 0.9727647, + 0.9649086, + 0.9552536, + 0.9727647, + 0.9649086, + 0.9552536 + ], + [ + 0.9760455, + 0.96897155, + 0.96035147, + 0.9760455, + 0.96897155, + 0.96035147, + 0.9760455, + 0.96897155, + 0.96035147 + ], + [ + 0.97915274, + 0.9727647, + 0.9649086, + 0.97915274, + 0.9727647, + 0.9649086, + 0.97915274, + 0.9727647, + 0.9649086 + ], + [ + 0.98185575, + 0.9760455, + 0.96897155, + 0.98185575, + 0.9760455, + 0.96897155, + 0.98185575, + 0.9760455, + 0.96897155 + ], + [ + 0.98434585, + 0.97915274, + 0.9727647, + 0.98434585, + 0.97915274, + 0.9727647, + 0.98434585, + 0.97915274, + 0.9727647 + ], + [ + 0.9866356, + 0.98185575, + 0.9760455, + 0.9866356, + 0.98185575, + 0.9760455, + 0.9866356, + 0.98185575, + 0.9760455 + ], + [ + 0.98856884, + 0.98434585, + 0.97915274, + 0.98856884, + 0.98434585, + 0.97915274, + 0.98856884, + 0.98434585, + 0.97915274 + ], + [ + 0.99050206, + 0.9866356, + 0.98185575, + 0.99050206, + 0.9866356, + 0.98185575, + 0.99050206, + 0.9866356, + 0.98185575 + ], + [ + 0.9920071, + 0.98856884, + 0.98434585, + 0.9920071, + 0.98856884, + 0.98434585, + 0.9920071, + 0.98856884, + 0.98434585 + ], + [ + 0.99343646, + 0.99050206, + 0.9866356, + 0.99343646, + 0.99050206, + 0.9866356, + 0.99343646, + 0.99050206, + 0.9866356 + ], + [ + 0.99481374, + 0.9920071, + 0.98856884, + 0.99481374, + 0.9920071, + 0.98856884, + 0.99481374, + 0.9920071, + 0.98856884 + ], + [ + 0.99578595, + 0.99343646, + 0.99050206, + 0.99578595, + 0.99343646, + 0.99050206, + 0.99578595, + 0.99343646, + 0.99050206 + ], + [ + 0.9967581, + 0.99481374, + 0.9920071, + 0.9967581, + 0.99481374, + 0.9920071, + 0.9967581, + 0.99481374, + 0.9920071 + ], + [ + 0.9976717, + 0.99578595, + 0.99343646, + 0.9976717, + 0.99578595, + 0.99343646, + 0.9976717, + 0.99578595, + 0.99343646 + ], + [ + 0.99822795, + 0.9967581, + 0.99481374, + 0.99822795, + 0.9967581, + 0.99481374, + 0.99822795, + 0.9967581, + 0.99481374 + ], + [ + 0.99878407, + 0.9976717, + 0.99578595, + 0.99878407, + 0.9976717, + 0.99578595, + 0.99878407, + 0.9976717, + 0.99578595 + ], + [ + 0.9993403, + 0.99822795, + 0.9967581, + 0.9993403, + 0.99822795, + 0.9967581, + 0.9993403, + 0.99822795, + 0.9967581 + ], + [ + 0.99954754, + 0.99878407, + 0.9976717, + 0.99954754, + 0.99878407, + 0.9976717, + 0.99954754, + 0.99878407, + 0.9976717 + ], + [ + 0.9997241, + 0.9993403, + 0.99822795, + 0.9997241, + 0.9993403, + 0.99822795, + 0.9997241, + 0.9993403, + 0.99822795 + ], + [ + 0.9999007, + 0.99954754, + 0.99878407, + 0.9999007, + 0.99954754, + 0.99878407, + 0.9999007, + 0.99954754, + 0.99878407 + ], + [ + 1, + 0.9997241, + 0.9993403, + 1, + 0.9997241, + 0.9993403, + 1, + 0.9997241, + 0.9993403 + ], + [ + 1, + 0.9999007, + 0.99954754, + 1, + 0.9999007, + 0.99954754, + 1, + 0.9999007, + 0.99954754 + ], + [ + 1, + 1, + 0.9997241, + 1, + 1, + 0.9997241, + 1, + 1, + 0.9997241 + ], + [ + 1, + 1, + 0.9999007, + 1, + 1, + 0.9999007, + 1, + 1, + 0.9999007 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/doubleClick_swapSide.json b/packages/SystemUI/tests/goldens/doubleClick_swapSide.json new file mode 100644 index 000000000000..044ddbc133b2 --- /dev/null +++ b/packages/SystemUI/tests/goldens/doubleClick_swapSide.json @@ -0,0 +1,195 @@ +{ + "frame_ids": [ + "before", + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + "after" + ], + "features": [ + { + "name": "userSwitcher_pos", + "type": "dpOffset", + "data_points": [ + { + "x": 0, + "y": 96 + }, + { + "x": 0, + "y": 96 + }, + { + "x": 0, + "y": 96 + }, + { + "x": 0, + "y": 96 + }, + { + "x": 45.008995, + "y": 96 + }, + { + "x": 123.20912, + "y": 96 + }, + { + "x": 194.33762, + "y": 96 + }, + { + "x": 248.24419, + "y": 96 + }, + { + "x": 285.66364, + "y": 96 + }, + { + "x": 310.326, + "y": 96 + }, + { + "x": 326.03296, + "y": 96 + }, + { + "x": 335.79584, + "y": 96 + }, + { + "x": 341.7547, + "y": 96 + }, + { + "x": 345.34082, + "y": 96 + }, + { + "x": 350.4762, + "y": 96 + } + ] + }, + { + "name": "userSwitcher_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 0.44034958, + 0, + 0, + 0, + 0.24635443, + 0.49282384, + 0.67560554, + 0.7993379, + 0.8786727, + 0.9278107, + 1 + ] + }, + { + "name": "foldAware_pos", + "type": "dpOffset", + "data_points": [ + { + "x": 350.4762, + "y": 96 + }, + { + "x": 350.4762, + "y": 96 + }, + { + "x": 350.4762, + "y": 96 + }, + { + "x": 350.4762, + "y": 96 + }, + { + "x": 305.4672, + "y": 96 + }, + { + "x": 227.26706, + "y": 96 + }, + { + "x": 156.13857, + "y": 96 + }, + { + "x": 102.232, + "y": 96 + }, + { + "x": 64.81257, + "y": 96 + }, + { + "x": 40.150204, + "y": 96 + }, + { + "x": 24.443243, + "y": 96 + }, + { + "x": 14.680362, + "y": 96 + }, + { + "x": 8.721494, + "y": 96 + }, + { + "x": 5.1353703, + "y": 96 + }, + { + "x": 0, + "y": 96 + } + ] + }, + { + "name": "foldAware_alpha", + "type": "float", + "data_points": [ + 1, + 1, + 1, + 1, + 0.44034958, + 0, + 0, + 0, + 0.24635443, + 0.49282384, + 0.67560554, + 0.7993379, + 0.8786727, + 0.9278107, + 1 + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/goldens/entryAnimation.json b/packages/SystemUI/tests/goldens/entryAnimation.json new file mode 100644 index 000000000000..11bf09f8bcd8 --- /dev/null +++ b/packages/SystemUI/tests/goldens/entryAnimation.json @@ -0,0 +1,823 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272, + 288, + 304, + 320, + 336, + 352, + 368, + 384, + 400, + 416, + 432, + 448, + 464, + 480, + 496, + 512, + 528, + "after" + ], + "features": [ + { + "name": "PinBouncer::dotAppearFadeIn", + "type": "float[]", + "data_points": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0.13006775, + 0, + 0, + 0.13006775, + 0, + 0, + 0.13006775, + 0, + 0 + ], + [ + 0.23268975, + 0, + 0, + 0.23268975, + 0, + 0, + 0.23268975, + 0, + 0 + ], + [ + 0.31893033, + 0.12296748, + 0, + 0.31893033, + 0.12296748, + 0, + 0.31893033, + 0.12296748, + 0 + ], + [ + 0.3938481, + 0.22658443, + 0, + 0.3938481, + 0.22658443, + 0, + 0.3938481, + 0.22658443, + 0 + ], + [ + 0.45988238, + 0.3139104, + 0.115867205, + 0.45988238, + 0.3139104, + 0.115867205, + 0.45988238, + 0.3139104, + 0.115867205 + ], + [ + 0.52037334, + 0.38916573, + 0.22036704, + 0.52037334, + 0.38916573, + 0.22036704, + 0.52037334, + 0.38916573, + 0.22036704 + ], + [ + 0.57470226, + 0.45587063, + 0.3084957, + 0.57470226, + 0.45587063, + 0.3084957, + 0.57470226, + 0.45587063, + 0.3084957 + ], + [ + 0.6230466, + 0.5169778, + 0.38448337, + 0.6230466, + 0.5169778, + 0.38448337, + 0.6230466, + 0.5169778, + 0.38448337 + ], + [ + 0.6682857, + 0.5713067, + 0.45185885, + 0.6682857, + 0.5713067, + 0.45185885, + 0.6682857, + 0.5713067, + 0.45185885 + ], + [ + 0.7079632, + 0.6202191, + 0.5135822, + 0.7079632, + 0.6202191, + 0.5135822, + 0.7079632, + 0.6202191, + 0.5135822 + ], + [ + 0.7447962, + 0.6654583, + 0.56791115, + 0.7447962, + 0.6654583, + 0.56791115, + 0.7447962, + 0.6654583, + 0.56791115 + ], + [ + 0.77875835, + 0.7056612, + 0.6173917, + 0.77875835, + 0.7056612, + 0.6173917, + 0.77875835, + 0.7056612, + 0.6173917 + ], + [ + 0.80779475, + 0.74249417, + 0.66263086, + 0.80779475, + 0.74249417, + 0.66263086, + 0.80779475, + 0.74249417, + 0.66263086 + ], + [ + 0.83683115, + 0.77694356, + 0.7033591, + 0.83683115, + 0.77694356, + 0.7033591, + 0.83683115, + 0.77694356, + 0.7033591 + ], + [ + 0.86171645, + 0.80597997, + 0.7401921, + 0.86171645, + 0.80597997, + 0.7401921, + 0.86171645, + 0.80597997, + 0.7401921 + ], + [ + 0.884127, + 0.83501637, + 0.7751288, + 0.884127, + 0.83501637, + 0.7751288, + 0.884127, + 0.83501637, + 0.7751288 + ], + [ + 0.9042124, + 0.86024225, + 0.8041652, + 0.9042124, + 0.86024225, + 0.8041652, + 0.9042124, + 0.86024225, + 0.8041652 + ], + [ + 0.9215058, + 0.8828717, + 0.8332016, + 0.9215058, + 0.8828717, + 0.8332016, + 0.9215058, + 0.8828717, + 0.8332016 + ], + [ + 0.9374617, + 0.9029571, + 0.8587681, + 0.9374617, + 0.9029571, + 0.8587681, + 0.9374617, + 0.9029571, + 0.8587681 + ], + [ + 0.95089734, + 0.92046183, + 0.88161635, + 0.95089734, + 0.92046183, + 0.88161635, + 0.95089734, + 0.92046183, + 0.88161635 + ], + [ + 0.9626156, + 0.93662196, + 0.90170175, + 0.9626156, + 0.93662196, + 0.90170175, + 0.9626156, + 0.93662196, + 0.90170175 + ], + [ + 0.97289115, + 0.95005757, + 0.91941786, + 0.97289115, + 0.95005757, + 0.91941786, + 0.97289115, + 0.95005757, + 0.91941786 + ], + [ + 0.98082036, + 0.96197337, + 0.9357822, + 0.98082036, + 0.96197337, + 0.9357822, + 0.98082036, + 0.96197337, + 0.9357822 + ], + [ + 0.98803866, + 0.9722489, + 0.94921786, + 0.98803866, + 0.9722489, + 0.94921786, + 0.98803866, + 0.9722489, + 0.94921786 + ], + [ + 0.99259704, + 0.98036927, + 0.9613311, + 0.99259704, + 0.98036927, + 0.9613311, + 0.99259704, + 0.98036927, + 0.9613311 + ], + [ + 0.99685574, + 0.9875875, + 0.97160673, + 0.99685574, + 0.9875875, + 0.97160673, + 0.99685574, + 0.9875875, + 0.97160673 + ], + [ + 0.9984336, + 0.99233085, + 0.9799181, + 0.9984336, + 0.99233085, + 0.9799181, + 0.9984336, + 0.99233085, + 0.9799181 + ], + [ + 0.99982595, + 0.99658954, + 0.98713636, + 0.99982595, + 0.99658954, + 0.98713636, + 0.99982595, + 0.99658954, + 0.98713636 + ], + [ + 1, + 0.99834657, + 0.99206465, + 1, + 0.99834657, + 0.99206465, + 1, + 0.99834657, + 0.99206465 + ], + [ + 1, + 0.99973893, + 0.9963234, + 1, + 0.99973893, + 0.9963234, + 1, + 0.99973893, + 0.9963234 + ], + [ + 1, + 1, + 0.99825954, + 1, + 1, + 0.99825954, + 1, + 1, + 0.99825954 + ], + [ + 1, + 1, + 0.9996519, + 1, + 1, + 0.9996519, + 1, + 1, + 0.9996519 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + ] + }, + { + "name": "PinBouncer::dotAppearMoveUp", + "type": "float[]", + "data_points": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0.25108898, + 0.24185245, + 0.23379733, + 0.25108898, + 0.24185245, + 0.23379733, + 0.25108898, + 0.24185245, + 0.23379733 + ], + [ + 0.36994478, + 0.35579002, + 0.34344575, + 0.36994478, + 0.35579002, + 0.34344575, + 0.36994478, + 0.35579002, + 0.34344575 + ], + [ + 0.45856017, + 0.44208717, + 0.4277212, + 0.45856017, + 0.44208717, + 0.4277212, + 0.45856017, + 0.44208717, + 0.4277212 + ], + [ + 0.5303175, + 0.5132119, + 0.49780974, + 0.5303175, + 0.5132119, + 0.49780974, + 0.5303175, + 0.5132119, + 0.49780974 + ], + [ + 0.5929084, + 0.57152635, + 0.5528793, + 0.5929084, + 0.57152635, + 0.5528793, + 0.5929084, + 0.57152635, + 0.5528793 + ], + [ + 0.6415321, + 0.6216319, + 0.6042771, + 0.6415321, + 0.6216319, + 0.6042771, + 0.6415321, + 0.6216319, + 0.6042771 + ], + [ + 0.6889181, + 0.6668597, + 0.64661235, + 0.6889181, + 0.6668597, + 0.64661235, + 0.6889181, + 0.6668597, + 0.64661235 + ], + [ + 0.72852343, + 0.70699567, + 0.6879909, + 0.72852343, + 0.70699567, + 0.6879909, + 0.72852343, + 0.70699567, + 0.6879909 + ], + [ + 0.763233, + 0.7418899, + 0.7227609, + 0.763233, + 0.7418899, + 0.7227609, + 0.763233, + 0.7418899, + 0.7227609 + ], + [ + 0.7938958, + 0.7733934, + 0.75354666, + 0.7938958, + 0.7733934, + 0.75354666, + 0.7938958, + 0.7733934, + 0.75354666 + ], + [ + 0.82150793, + 0.8013512, + 0.7816832, + 0.82150793, + 0.8013512, + 0.7816832, + 0.82150793, + 0.8013512, + 0.7816832 + ], + [ + 0.84668195, + 0.82613826, + 0.807758, + 0.84668195, + 0.82613826, + 0.807758, + 0.84668195, + 0.82613826, + 0.807758 + ], + [ + 0.86843777, + 0.84911424, + 0.83017635, + 0.86843777, + 0.84911424, + 0.83017635, + 0.86843777, + 0.84911424, + 0.83017635 + ], + [ + 0.88804924, + 0.86938363, + 0.85123545, + 0.88804924, + 0.86938363, + 0.85123545, + 0.88804924, + 0.86938363, + 0.85123545 + ], + [ + 0.90616417, + 0.8875992, + 0.87020856, + 0.90616417, + 0.8875992, + 0.87020856, + 0.90616417, + 0.8875992, + 0.87020856 + ], + [ + 0.92120105, + 0.90447646, + 0.8872067, + 0.92120105, + 0.90447646, + 0.8872067, + 0.92120105, + 0.90447646, + 0.8872067 + ], + [ + 0.93561906, + 0.91881925, + 0.9030046, + 0.93561906, + 0.91881925, + 0.9030046, + 0.93561906, + 0.91881925, + 0.9030046 + ], + [ + 0.9472463, + 0.9325603, + 0.91674215, + 0.9472463, + 0.9325603, + 0.91674215, + 0.9472463, + 0.9325603, + 0.91674215 + ], + [ + 0.95841366, + 0.9437798, + 0.9296044, + 0.95841366, + 0.9437798, + 0.9296044, + 0.95841366, + 0.9437798, + 0.9296044 + ], + [ + 0.9671384, + 0.9546126, + 0.9407567, + 0.9671384, + 0.9546126, + 0.9407567, + 0.9671384, + 0.9546126, + 0.9407567 + ], + [ + 0.97568256, + 0.96334505, + 0.95089674, + 0.97568256, + 0.96334505, + 0.95089674, + 0.97568256, + 0.96334505, + 0.95089674 + ], + [ + 0.98170155, + 0.9714737, + 0.9600369, + 0.98170155, + 0.9714737, + 0.9600369, + 0.98170155, + 0.9714737, + 0.9600369 + ], + [ + 0.9877205, + 0.9782621, + 0.96764565, + 0.9877205, + 0.9782621, + 0.96764565, + 0.9877205, + 0.9782621, + 0.96764565 + ], + [ + 0.9916518, + 0.98386985, + 0.9752545, + 0.9916518, + 0.98386985, + 0.9752545, + 0.9916518, + 0.98386985, + 0.9752545 + ], + [ + 0.99514234, + 0.98918015, + 0.9805117, + 0.99514234, + 0.98918015, + 0.9805117, + 0.99514234, + 0.98918015, + 0.9805117 + ], + [ + 0.9976143, + 0.99243224, + 0.98576087, + 0.9976143, + 0.99243224, + 0.98576087, + 0.9976143, + 0.99243224, + 0.98576087 + ], + [ + 0.998737, + 0.9956844, + 0.9900688, + 0.998737, + 0.9956844, + 0.9900688, + 0.998737, + 0.9956844, + 0.9900688 + ], + [ + 0.9998597, + 0.99771196, + 0.9931129, + 0.9998597, + 0.99771196, + 0.9931129, + 0.9998597, + 0.99771196, + 0.9931129 + ], + [ + 1, + 0.9987579, + 0.99615705, + 1, + 0.9987579, + 0.99615705, + 1, + 0.9987579, + 0.99615705 + ], + [ + 1, + 0.9998039, + 0.9977971, + 1, + 0.9998039, + 0.9977971, + 1, + 0.9998039, + 0.9977971 + ], + [ + 1, + 1, + 0.99877614, + 1, + 1, + 0.99877614, + 1, + 1, + 0.99877614 + ], + [ + 1, + 1, + 0.9997552, + 1, + 1, + 0.9997552, + 1, + 1, + 0.9997552 + ], + [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ] + ] + } + ] +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index e72027a921b7..5702a8c61e7a 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -21,14 +21,18 @@ import android.view.View import android.view.ViewTreeObserver import android.widget.FrameLayout import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.Edge +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.core.LogLevel @@ -68,8 +72,9 @@ import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import com.android.systemui.Flags as AConfigFlags +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @SmallTest @@ -319,26 +324,16 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForDozeAmountTransition_updatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever( - keyguardTransitionInteractor.transition( - KeyguardState.LOCKSCREEN, - KeyguardState.AOD - ) - ) + whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD))) .thenReturn(transitionStep) - whenever( - keyguardTransitionInteractor.transition( - KeyguardState.AOD, - KeyguardState.LOCKSCREEN - ) - ) + whenever(keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN))) .thenReturn(transitionStep) val job = underTest.listenForDozeAmountTransition(this) transitionStep.value = TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, + from = LOCKSCREEN, + to = AOD, value = 0.4f, transitionState = TransitionState.RUNNING, ) @@ -353,14 +348,14 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForTransitionToAodFromGone_updatesClockDozeAmountToOne() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) + whenever(keyguardTransitionInteractor.transitionStepsToState(AOD)) .thenReturn(transitionStep) val job = underTest.listenForAnyStateToAodTransition(this) transitionStep.value = TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, + from = GONE, + to = AOD, transitionState = TransitionState.STARTED, ) yield() @@ -371,19 +366,19 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test - fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToOne() = + fun listenForTransitionToLSFromOccluded_updatesClockDozeAmountToZero() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN)) - .thenReturn(transitionStep) + whenever(keyguardTransitionInteractor.transitionStepsToState(LOCKSCREEN)) + .thenReturn(transitionStep) val job = underTest.listenForAnyStateToLockscreenTransition(this) transitionStep.value = - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.LOCKSCREEN, - transitionState = TransitionState.STARTED, - ) + TransitionStep( + from = OCCLUDED, + to = LOCKSCREEN, + transitionState = TransitionState.STARTED, + ) yield() verify(animations, times(2)).doze(0f) @@ -395,37 +390,37 @@ class ClockEventControllerTest : SysuiTestCase() { fun listenForTransitionToAodFromLockscreen_neverUpdatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.AOD)) + whenever(keyguardTransitionInteractor.transitionStepsToState(AOD)) .thenReturn(transitionStep) val job = underTest.listenForAnyStateToAodTransition(this) transitionStep.value = TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, + from = LOCKSCREEN, + to = AOD, transitionState = TransitionState.STARTED, ) yield() verify(animations, never()).doze(1f) - job.cancel() - } + job.cancel() + } @Test fun listenForAnyStateToLockscreenTransition_neverUpdatesClockDozeAmount() = runBlocking(IMMEDIATE) { val transitionStep = MutableStateFlow(TransitionStep()) - whenever(keyguardTransitionInteractor.transitionStepsToState(KeyguardState.LOCKSCREEN)) - .thenReturn(transitionStep) + whenever(keyguardTransitionInteractor.transitionStepsToState(LOCKSCREEN)) + .thenReturn(transitionStep) val job = underTest.listenForAnyStateToLockscreenTransition(this) transitionStep.value = - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - transitionState = TransitionState.STARTED, - ) + TransitionStep( + from = AOD, + to = LOCKSCREEN, + transitionState = TransitionState.STARTED, + ) yield() verify(animations, never()).doze(0f) @@ -434,6 +429,27 @@ class ClockEventControllerTest : SysuiTestCase() { } @Test + fun listenForAnyStateToDozingTransition_UpdatesClockDozeAmountToOne() = + runBlocking(IMMEDIATE) { + val transitionStep = MutableStateFlow(TransitionStep()) + whenever(keyguardTransitionInteractor.transitionStepsToState(DOZING)) + .thenReturn(transitionStep) + + val job = underTest.listenForAnyStateToDozingTransition(this) + transitionStep.value = + TransitionStep( + from = LOCKSCREEN, + to = 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/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java index 51ceda339778..f9fe5e7e2b62 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.os.SystemClock; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.KeyEvent; @@ -125,8 +126,8 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) public void withFeatureFlagOn_oldMessage_isHidden() { - mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES); KeyguardAbsKeyInputViewController underTest = createTestObject(); underTest.init(); 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/WindowMagnificationSizePrefsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java index 516b6658d24a..93c0eeaac488 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSizePrefsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationFrameSizePrefsTest.java @@ -39,9 +39,9 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class WindowMagnificationSizePrefsTest extends SysuiTestCase { +public class WindowMagnificationFrameSizePrefsTest extends SysuiTestCase { - WindowMagnificationSizePrefs mWindowMagnificationSizePrefs; + WindowMagnificationFrameSizePrefs mWindowMagnificationFrameSizePrefs; FakeSharedPreferences mSharedPreferences; @Before @@ -51,24 +51,24 @@ public class WindowMagnificationSizePrefsTest extends SysuiTestCase { when(mContext.getSharedPreferences( eq("window_magnification_preferences"), anyInt())) .thenReturn(mSharedPreferences); - mWindowMagnificationSizePrefs = new WindowMagnificationSizePrefs(mContext); + mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext); } @Test public void saveSizeForCurrentDensity_getExpectedSize() { Size testSize = new Size(500, 500); - mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize); + mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize); - assertThat(mWindowMagnificationSizePrefs.getSizeForCurrentDensity()) + assertThat(mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity()) .isEqualTo(testSize); } @Test public void saveSizeForCurrentDensity_containsPreferenceForCurrentDensity() { Size testSize = new Size(500, 500); - mWindowMagnificationSizePrefs.saveSizeForCurrentDensity(testSize); + mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(testSize); - assertThat(mWindowMagnificationSizePrefs.isPreferenceSavedForCurrentDensity()) + assertThat(mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) .isTrue(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index e0df1e0e5586..2d5e3a6788cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import android.graphics.PointF; -import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -40,7 +39,6 @@ import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; import androidx.test.filters.SmallTest; -import com.android.systemui.Flags; import com.android.systemui.Prefs; import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.utils.TestUtils; @@ -230,7 +228,6 @@ public class MenuAnimationControllerTest extends SysuiTestCase { } @Test - @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK) public void tuck_animates() { mMenuAnimationController.cancelAnimations(); mMenuAnimationController.moveToEdgeAndHide(); @@ -239,7 +236,6 @@ public class MenuAnimationControllerTest extends SysuiTestCase { } @Test - @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK) public void untuck_animates() { mMenuAnimationController.cancelAnimations(); mMenuAnimationController.moveOutEdgeAndShow(); 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/accessibility/hearingaid/HearingDevicesDialogManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java index abc12ede0a67..e9c742d63d81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogManagerTest.java @@ -24,7 +24,6 @@ import static org.mockito.Mockito.when; import android.bluetooth.BluetoothDevice; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.view.View; import androidx.test.filters.SmallTest; @@ -34,6 +33,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.statusbar.phone.SystemUIDialog; import org.junit.Before; @@ -56,9 +56,10 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); - private final View mView = new View(mContext); private final List<CachedBluetoothDevice> mCachedDevices = new ArrayList<>(); @Mock + private Expandable mExpandable; + @Mock private DialogTransitionAnimator mDialogTransitionAnimator; @Mock private HearingDevicesDialogDelegate.Factory mDialogFactory; @@ -97,7 +98,7 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { public void showDialog_bluetoothDisable_showPairNewDeviceTrue() { when(mLocalBluetoothAdapter.isEnabled()).thenReturn(false); - mManager.showDialog(mView); + mManager.showDialog(mExpandable); verify(mDialogFactory).create(eq(true)); } @@ -109,7 +110,7 @@ public class HearingDevicesDialogManagerTest extends SysuiTestCase { when(mCachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); mCachedDevices.add(mCachedDevice); - mManager.showDialog(mView); + mManager.showDialog(mExpandable); verify(mDialogFactory).create(eq(false)); } 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 fbe11847f3d3..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,13 +25,18 @@ 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 +import platform.test.motion.MotionTestRule import platform.test.motion.RecordedMotion -import platform.test.motion.Sampling.Companion.evenlySampled +import platform.test.motion.view.AnimationSampling.Companion.evenlySampled import platform.test.motion.view.DrawableFeatureCaptures -import platform.test.motion.view.ViewMotionTestRule +import platform.test.motion.view.ViewRecordingSpec.Companion.captureWithoutScreenshot +import platform.test.motion.view.ViewToolkit +import platform.test.motion.view.record import platform.test.screenshot.DeviceEmulationRule import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.DisplaySpec @@ -55,16 +60,19 @@ 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 ) @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(emulationSpec) @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java) - @get:Rule(order = 2) val motionRule = ViewMotionTestRule(pathManager, { activityRule.scenario }) + @get:Rule(order = 2) + val motionRule = MotionTestRule(ViewToolkit { activityRule.scenario }, pathManager) @Test fun backgroundAnimation_whenLaunching() { @@ -151,15 +159,14 @@ class TransitionAnimatorTest : SysuiTestCase() { backgroundLayer: GradientDrawable, animator: AnimatorSet ): RecordedMotion { - return motionRule.checkThat(animator).record( - backgroundLayer, - evenlySampled(20), - visualCapture = null - ) { - capture(DrawableFeatureCaptures.bounds, "bounds") - capture(DrawableFeatureCaptures.cornerRadii, "corner_radii") - capture(DrawableFeatureCaptures.alpha, "alpha") - } + return motionRule.record( + animator, + backgroundLayer.captureWithoutScreenshot(evenlySampled(20)) { + feature(DrawableFeatureCaptures.bounds, "bounds") + feature(DrawableFeatureCaptures.cornerRadii, "corner_radii") + feature(DrawableFeatureCaptures.alpha, "alpha") + } + ) } } 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/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt index 67ca9a40dc2a..023148603b50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -16,80 +16,73 @@ package com.android.systemui.biometrics +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.biometrics.domain.BiometricsDomainLayerModule -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FakeFeatureFlagsClassicModule -import com.android.systemui.flags.Flags -import com.android.systemui.runCurrent -import com.android.systemui.runTest -import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.user.domain.UserDomainLayerModule -import dagger.BindsInstance -import dagger.Component +import com.android.systemui.flags.andSceneContainer +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.shadeTestUtil +import com.android.systemui.testKosmos +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.verify -import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations +import org.mockito.kotlin.verifyZeroInteractions +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { - - @SysUISingleton - @Component( - modules = - [ - SysUITestModule::class, - UserDomainLayerModule::class, - BiometricsDomainLayerModule::class, - ] - ) - interface TestComponent : SysUITestComponent<AuthDialogPanelInteractionDetector> { - - val shadeRepository: FakeShadeRepository - - @Component.Factory - interface Factory { - fun create( - @BindsInstance test: SysuiTestCase, - featureFlags: FakeFeatureFlagsClassicModule, - ): TestComponent +@RunWith(ParameterizedAndroidJunit4::class) +class AuthDialogPanelInteractionDetectorTest(flags: FlagsParameterization?) : SysuiTestCase() { + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() } } - private val testComponent: TestComponent = - DaggerAuthDialogPanelInteractionDetectorTest_TestComponent.factory() - .create( - test = this, - featureFlags = - FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) }, - ) + init { + mSetFlagsRule.setFlagsParameterization(flags!!) + } - private val detector: AuthDialogPanelInteractionDetector = testComponent.underTest + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } @Mock private lateinit var action: Runnable + lateinit var detector: AuthDialogPanelInteractionDetector + @Before fun setUp() { MockitoAnnotations.initMocks(this) + detector = + AuthDialogPanelInteractionDetector( + kosmos.applicationCoroutineScope, + { kosmos.shadeInteractor }, + ) } @Test fun enableDetector_expand_shouldRunAction() = - testComponent.runTest { + testScope.runTest { // GIVEN shade is closed and detector is enabled - shadeRepository.setLegacyShadeExpansion(0f) + shadeTestUtil.setShadeExpansion(0f) detector.enable(action) runCurrent() // WHEN shade expands - shadeRepository.setLegacyShadeTracking(true) - shadeRepository.setLegacyShadeExpansion(.5f) + shadeTestUtil.setTracking(true) + shadeTestUtil.setShadeExpansion(.5f) runCurrent() // THEN action was run @@ -98,9 +91,9 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { @Test fun enableDetector_isUserInteractingTrue_shouldNotPostRunnable() = - testComponent.runTest { + testScope.runTest { // GIVEN isInteracting starts true - shadeRepository.setLegacyShadeTracking(true) + shadeTestUtil.setTracking(true) runCurrent() detector.enable(action) @@ -110,33 +103,34 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { @Test fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() = - testComponent.runTest { + testScope.runTest { // GIVEN shade is closed and detector is enabled - shadeRepository.setLegacyShadeExpansion(0f) + shadeTestUtil.setShadeExpansion(0f) detector.enable(action) runCurrent() // WHEN shade expands fully instantly - shadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) runCurrent() // THEN action not run verifyZeroInteractions(action) + detector.disable() } @Test fun disableDetector_shouldNotPostRunnable() = - testComponent.runTest { + testScope.runTest { // GIVEN shade is closed and detector is enabled - shadeRepository.setLegacyShadeExpansion(0f) + shadeTestUtil.setShadeExpansion(0f) detector.enable(action) runCurrent() // WHEN detector is disabled and shade opens detector.disable() runCurrent() - shadeRepository.setLegacyShadeTracking(true) - shadeRepository.setLegacyShadeExpansion(.5f) + shadeTestUtil.setTracking(true) + shadeTestUtil.setShadeExpansion(.5f) runCurrent() // THEN action not run @@ -145,17 +139,18 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { @Test fun enableDetector_beginCollapse_shouldNotPostRunnable() = - testComponent.runTest { + testScope.runTest { // GIVEN shade is open and detector is enabled - shadeRepository.setLegacyShadeExpansion(1f) + shadeTestUtil.setShadeExpansion(1f) detector.enable(action) runCurrent() // WHEN shade begins to collapse - shadeRepository.setLegacyShadeExpansion(.5f) + shadeTestUtil.programmaticCollapseShade() runCurrent() // THEN action not run verifyZeroInteractions(action) + detector.disable() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt index 0df4fbf86d98..9ba56d27fdf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt @@ -36,12 +36,12 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any private const val USER_ID = 8 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..2682633f5dfd 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 @@ -49,6 +44,8 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit private const val USER_ID = 9 +private const val REQUEST_ID = 9L +private const val WRONG_REQUEST_ID = 10L private const val CHALLENGE = 90L private const val OP_PACKAGE_NAME = "biometric.testapp" @@ -110,6 +107,7 @@ class PromptRepositoryImplTest : SysuiTestCase() { repository.setPrompt( PromptInfo().apply { isConfirmationRequested = case }, USER_ID, + REQUEST_ID, CHALLENGE, PromptKind.Biometric(), OP_PACKAGE_NAME @@ -129,6 +127,7 @@ class PromptRepositoryImplTest : SysuiTestCase() { repository.setPrompt( PromptInfo().apply { isConfirmationRequested = case }, USER_ID, + REQUEST_ID, CHALLENGE, PromptKind.Biometric(), OP_PACKAGE_NAME @@ -139,101 +138,46 @@ class PromptRepositoryImplTest : SysuiTestCase() { } @Test - fun showBpWithoutIconForCredential_withVerticalListContentView() = + fun setsAndUnsetsPrompt_whenRequestIdMatches() = 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) - } - } + val kind = PromptKind.Pin + val promptInfo = PromptInfo() - @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() - } - } + repository.setPrompt(promptInfo, USER_ID, REQUEST_ID, CHALLENGE, kind, OP_PACKAGE_NAME) - @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() - } + 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) + assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME) + + repository.unsetPrompt(REQUEST_ID) + + assertThat(repository.promptInfo.value).isNull() + assertThat(repository.userId.value).isNull() + assertThat(repository.challenge.value).isNull() + assertThat(repository.opPackageName.value).isNull() } @Test - fun setsAndUnsetsPrompt() = + fun setsAndUnsetsPrompt_whenRequestIdDoesNotMatch() = testScope.runTest { val kind = PromptKind.Pin val promptInfo = PromptInfo() - repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME) + repository.setPrompt(promptInfo, USER_ID, REQUEST_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) assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME) - repository.unsetPrompt() + repository.unsetPrompt(WRONG_REQUEST_ID) - assertThat(repository.promptInfo.value).isNull() - assertThat(repository.userId.value).isNull() - assertThat(repository.challenge.value).isNull() - assertThat(repository.opPackageName.value).isNull() + assertThat(repository.promptInfo.value).isNotNull() + assertThat(repository.userId.value).isNotNull() + assertThat(repository.challenge.value).isNotNull() + assertThat(repository.opPackageName.value).isNotNull() } } 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..c4d0d23ce9f3 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 @@ -33,6 +33,7 @@ import org.junit.runners.JUnit4 import org.mockito.junit.MockitoJUnit private const val USER_ID = 22 +private const val REQUEST_ID = 22L private const val OPERATION_ID = 100L private const val OP_PACKAGE_NAME = "biometric.testapp" @@ -110,8 +111,9 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.description = description it.subtitle = subtitle }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, + requestId = REQUEST_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME ) @@ -135,8 +137,9 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.subtitle = subtitle it.contentView = contentView }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, + requestId = REQUEST_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME ) @@ -163,21 +166,22 @@ class PromptCredentialInteractorTest : SysuiTestCase() { it.subtitle = subtitle it.contentView = contentView }, - kind = Utils.CREDENTIAL_PIN, + kind = PromptKind.Pin, userId = USER_ID, + requestId = REQUEST_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME ) 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 @@ -198,6 +202,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { }, kind = kind, userId = USER_ID, + requestId = REQUEST_ID, challenge = OPERATION_ID, opPackageName = OP_PACKAGE_NAME ) @@ -211,11 +216,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") } ) @@ -224,7 +228,7 @@ class PromptCredentialInteractorTest : SysuiTestCase() { assertThat(pattern.stealthMode).isEqualTo(isStealth) } - interactor.resetPrompt() + interactor.resetPrompt(REQUEST_ID) assertThat(prompt).isNull() } @@ -341,6 +345,30 @@ 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, + requestId: Long, + challenge: Long, + opPackageName: String, + ) { + biometricPromptRepository.setPrompt( + promptInfo, + userId, + requestId, + challenge, + kind, + opPackageName, + ) + } + + /** Unset the current authentication request. */ + private fun PromptCredentialInteractor.resetPrompt(requestId: Long) { + biometricPromptRepository.unsetPrompt(requestId) + } } 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..3102a84b852a 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,23 @@ 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 REQUEST_ID = 8L + 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 +73,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 +83,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 +108,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 +134,28 @@ 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, + REQUEST_ID, modalities, - OP_PACKAGE_NAME + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ ) assertThat(currentPrompt).isNotNull() @@ -128,36 +164,184 @@ 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() + interactor.resetPrompt(REQUEST_ID) 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, + REQUEST_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind?.isBiometric()).isTrue() + + interactor.resetPrompt(REQUEST_ID) + 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, + REQUEST_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + true /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) - private fun TestScope.useCredentialAndReset(@Utils.CredentialType kind: Int) { + interactor.resetPrompt(REQUEST_ID) + verifyUnset() + } + + @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, + REQUEST_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) + + interactor.resetPrompt(REQUEST_ID) + 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, + REQUEST_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind).isEqualTo(PromptKind.Password) + + interactor.resetPrompt(REQUEST_ID) + 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, + REQUEST_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + false /*onSwitchToCredential*/ + ) + + assertThat(promptKind?.isBiometric()).isTrue() + + interactor.resetPrompt(REQUEST_ID) + 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,25 +359,36 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { assertThat(currentPrompt).isNull() - interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME) + interactor.setPrompt( + info, + USER_ID, + REQUEST_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() + interactor.resetPrompt(REQUEST_ID) verifyUnset() } 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/CredentialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt index 3245020ec584..9e804c123520 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/CredentialViewModelTest.kt @@ -22,6 +22,7 @@ import org.junit.runner.RunWith import org.junit.runners.JUnit4 private const val USER_ID = 9 +private const val REQUEST_ID = 9L private const val OPERATION_ID = 10L @OptIn(ExperimentalCoroutinesApi::class) @@ -171,7 +172,7 @@ class CredentialViewModelTest : SysuiTestCase() { ) = runTest(dispatcher) { init() - promptRepository.setPrompt(promptInfo(), USER_ID, OPERATION_ID, kind) + promptRepository.setPrompt(promptInfo(), USER_ID, REQUEST_ID, OPERATION_ID, kind) block() } } 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..53ccb906c546 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 @@ -33,10 +37,12 @@ import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.platform.test.annotations.EnableFlags 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 @@ -90,10 +96,12 @@ import org.mockito.Mock import org.mockito.junit.MockitoJUnit private const val USER_ID = 4 +private const val REQUEST_ID = 4L 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 +115,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 +182,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 +197,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 @@ -1257,8 +1274,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_BP_TALKBACK) fun hint_for_talkback_guidance() = runGenericTest { - mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK) val hint by collectLastValue(viewModel.accessibilityHint) // Touches should fall outside of sensor area @@ -1280,10 +1297,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun descriptionOverriddenByVerticalListContentView() = runGenericTest(contentView = promptContentView, description = "test description") { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1292,13 +1308,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun descriptionOverriddenByContentViewWithMoreOptionsButton() = runGenericTest( contentView = promptContentViewWithMoreOptionsButton, description = "test description" ) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1307,10 +1322,9 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) fun descriptionWithoutContentView() = runGenericTest(description = "test description") { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val contentView by collectLastValue(viewModel.contentView) val description by collectLastValue(viewModel.description) @@ -1319,62 +1333,84 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } @Test - fun logoIsNullIfPackageNameNotFound() = + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logo_nullIfPkgNameNotFound() = + runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { + val logo by collectLastValue(viewModel.logo) + assertThat(logo).isNull() + } + + @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logo_defaultWithOverrides() = + runGenericTest(packageName = packageNameForLogoWithOverrides) { + 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 + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logo_defaultIsNull() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isNull() } @Test - fun defaultLogoIfNoLogoSet() = runGenericTest { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logo_default() = runGenericTest { val logo by collectLastValue(viewModel.logo) assertThat(logo).isEqualTo(defaultLogoIcon) } @Test - fun logoResSetByApp() = + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logo_resSetByApp() = runGenericTest(logoRes = logoResFromApp) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat(logo).isEqualTo(logoFromApp) } @Test - fun logoBitmapSetByApp() = + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logo_bitmapSetByApp() = runGenericTest(logoBitmap = logoBitmapFromApp) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logo by collectLastValue(viewModel.logo) assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp) } @Test - fun logoDescriptionIsEmptyIfPackageNameNotFound() = + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logoDescription_emptyIfPkgNameNotFound() = + runGenericTest(packageName = OP_PACKAGE_NAME_CAN_NOT_BE_FOUND) { + val logoDescription by collectLastValue(viewModel.logoDescription) + assertThat(logoDescription).isEqualTo("") + } + + @Test + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logoDescription_defaultIsEmpty() = runGenericTest(packageName = OP_PACKAGE_NAME_NO_ICON) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo("") } @Test - fun defaultLogoDescriptionIfNoLogoDescriptionSet() = runGenericTest { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logoDescription_default() = runGenericTest { val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo(defaultLogoDescription) } @Test - fun logoDescriptionSetByApp() = + @EnableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT, FLAG_CONSTRAINT_BP) + fun logoDescription_setByApp() = runGenericTest(logoDescription = logoDescriptionFromApp) { - mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) - mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) val logoDescription by collectLastValue(viewModel.logoDescription) assertThat(logoDescription).isEqualTo(logoDescriptionFromApp) } @@ -1417,6 +1453,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(REQUEST_ID) + + viewModel = + PromptViewModel( + displayStateInteractor, + selector, + mContext, + udfpsOverlayInteractor, + biometricStatusInteractor, + udfpsUtils, + iconProvider, + activityTaskManager + ) + iconViewModel = viewModel.iconViewModel + selector.initializePrompt( requireConfirmation = testCase.confirmationRequested, allowCredentialFallback = allowCredentialFallback, @@ -1630,12 +1686,14 @@ private fun PromptSelectorInteractor.initializePrompt( isConfirmationRequested = requireConfirmation } - useBiometricsForAuthentication( + setPrompt( info, USER_ID, - CHALLENGE, + REQUEST_ID, 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/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt index b05d9591d8a8..11f74c0b98cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt @@ -17,12 +17,12 @@ package com.android.systemui.bluetooth.qsdialog import android.bluetooth.BluetoothAdapter +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.View.GONE import android.view.View.VISIBLE -import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice @@ -30,6 +30,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.settingslib.flags.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.FakeSharedPreferences @@ -63,6 +64,7 @@ import org.mockito.junit.MockitoRule @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) +@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE) class BluetoothTileDialogViewModelTest : SysuiTestCase() { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() @@ -77,6 +79,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor + @Mock private lateinit var deviceItemActionInteractor: DeviceItemActionInteractor + @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator @@ -100,6 +104,8 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Mock private lateinit var bluetoothTileDialogDelegate: BluetoothTileDialogDelegate @Mock private lateinit var sysuiDialog: SystemUIDialog + @Mock private lateinit var expandable: Expandable + @Mock private lateinit var controller: DialogTransitionAnimator.Controller private val sharedPreferences = FakeSharedPreferences() @@ -109,13 +115,13 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { @Before fun setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE) scheduler = TestCoroutineScheduler() dispatcher = UnconfinedTestDispatcher(scheduler) testScope = TestScope(dispatcher) bluetoothTileDialogViewModel = BluetoothTileDialogViewModel( deviceItemInteractor, + deviceItemActionInteractor, BluetoothStateInteractor( localBluetoothManager, bluetoothTileDialogLogger, @@ -157,6 +163,7 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { .thenReturn(getMutableStateFlow(false)) whenever(audioSharingInteractor.audioSharingButtonStateUpdate) .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone)) + whenever(expandable.dialogTransitionController(any())).thenReturn(controller) } @Test @@ -164,16 +171,16 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { testScope.runTest { bluetoothTileDialogViewModel.showDialog(null) - verify(mDialogTransitionAnimator, never()).showFromView(any(), any(), any(), any()) + verify(mDialogTransitionAnimator, never()).show(any(), any(), any()) } } @Test fun testShowDialog_animated() { testScope.runTest { - bluetoothTileDialogViewModel.showDialog(LinearLayout(mContext)) + bluetoothTileDialogViewModel.showDialog(expandable) - verify(mDialogTransitionAnimator).showFromView(any(), any(), nullable(), anyBoolean()) + verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean()) } } @@ -181,10 +188,9 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() { fun testShowDialog_animated_callInBackgroundThread() { testScope.runTest { backgroundExecutor.execute { - bluetoothTileDialogViewModel.showDialog(LinearLayout(mContext)) + bluetoothTileDialogViewModel.showDialog(expandable) - verify(mDialogTransitionAnimator) - .showFromView(any(), any(), nullable(), anyBoolean()) + verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean()) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorImplTest.kt new file mode 100644 index 000000000000..762137bede27 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorImplTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.bluetooth.qsdialog + +import android.bluetooth.BluetoothDevice +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.systemui.SysuiTestCase +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.whenever +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@OptIn(ExperimentalCoroutinesApi::class) +class DeviceItemActionInteractorImplTest : SysuiTestCase() { + @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() + private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() } + private lateinit var actionInteractorImpl: DeviceItemActionInteractor + + @Mock private lateinit var dialog: SystemUIDialog + @Mock private lateinit var cachedDevice: CachedBluetoothDevice + @Mock private lateinit var device: BluetoothDevice + @Mock private lateinit var deviceItem: DeviceItem + + @Before + fun setUp() { + actionInteractorImpl = kosmos.deviceItemActionInteractor + whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedDevice) + whenever(cachedDevice.address).thenReturn("ADDRESS") + whenever(cachedDevice.device).thenReturn(device) + } + + @Test + fun testOnClick_connectedMedia_setActive() { + with(kosmos) { + testScope.runTest { + whenever(deviceItem.type) + .thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) + actionInteractorImpl.onClick(deviceItem, dialog) + verify(cachedDevice).setActive() + verify(bluetoothTileDialogLogger) + .logDeviceClick( + cachedDevice.address, + DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE + ) + } + } + } + + @Test + fun testOnClick_activeMedia_disconnect() { + with(kosmos) { + testScope.runTest { + whenever(deviceItem.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) + actionInteractorImpl.onClick(deviceItem, dialog) + verify(cachedDevice).disconnect() + verify(bluetoothTileDialogLogger) + .logDeviceClick( + cachedDevice.address, + DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE + ) + } + } + } + + @Test + fun testOnClick_connectedOtherDevice_disconnect() { + with(kosmos) { + testScope.runTest { + whenever(deviceItem.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + actionInteractorImpl.onClick(deviceItem, dialog) + verify(cachedDevice).disconnect() + verify(bluetoothTileDialogLogger) + .logDeviceClick(cachedDevice.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) + } + } + } + + @Test + fun testOnClick_saved_connect() { + with(kosmos) { + testScope.runTest { + whenever(deviceItem.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE) + actionInteractorImpl.onClick(deviceItem, dialog) + verify(cachedDevice).connect() + verify(bluetoothTileDialogLogger) + .logDeviceClick(cachedDevice.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE) + } + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.kt new file mode 100644 index 000000000000..e8e37bc81866 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemActionInteractorKosmos.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.bluetooth.qsdialog + +import com.android.internal.logging.uiEventLogger +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.util.mockito.mock + +val Kosmos.bluetoothTileDialogLogger: BluetoothTileDialogLogger by Kosmos.Fixture { mock {} } + +val Kosmos.deviceItemActionInteractor: DeviceItemActionInteractor by + Kosmos.Fixture { + DeviceItemActionInteractorImpl( + testDispatcher, + bluetoothTileDialogLogger, + uiEventLogger, + ) + } 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/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt index daf4a3cbb9de..2b4f9503f371 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt @@ -23,7 +23,6 @@ import android.media.AudioManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest -import com.android.internal.logging.UiEventLogger import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.settingslib.bluetooth.LocalBluetoothManager import com.android.systemui.SysuiTestCase @@ -39,7 +38,6 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule @@ -71,8 +69,6 @@ class DeviceItemInteractorTest : SysuiTestCase() { @Mock private lateinit var localBluetoothManager: LocalBluetoothManager - @Mock private lateinit var uiEventLogger: UiEventLogger - @Mock private lateinit var logger: BluetoothTileDialogLogger private val fakeSystemClock = FakeSystemClock() @@ -94,7 +90,6 @@ class DeviceItemInteractorTest : SysuiTestCase() { adapter, localBluetoothManager, fakeSystemClock, - uiEventLogger, logger, testScope.backgroundScope, dispatcher @@ -218,61 +213,6 @@ class DeviceItemInteractorTest : SysuiTestCase() { } } - @Test - fun testUpdateDeviceItemOnClick_connectedMedia_setActive() { - testScope.runTest { - `when`(deviceItem1.type).thenReturn(DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE) - - interactor.updateDeviceItemOnClick(deviceItem1) - - verify(cachedDevice1).setActive() - verify(logger) - .logDeviceClick( - cachedDevice1.address, - DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE - ) - } - } - - @Test - fun testUpdateDeviceItemOnClick_activeMedia_disconnect() { - testScope.runTest { - `when`(deviceItem1.type).thenReturn(DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) - - interactor.updateDeviceItemOnClick(deviceItem1) - - verify(cachedDevice1).disconnect() - verify(logger) - .logDeviceClick(cachedDevice1.address, DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE) - } - } - - @Test - fun testUpdateDeviceItemOnClick_connectedOtherDevice_disconnect() { - testScope.runTest { - `when`(deviceItem1.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) - - interactor.updateDeviceItemOnClick(deviceItem1) - - verify(cachedDevice1).disconnect() - verify(logger) - .logDeviceClick(cachedDevice1.address, DeviceItemType.CONNECTED_BLUETOOTH_DEVICE) - } - } - - @Test - fun testUpdateDeviceItemOnClick_saved_connect() { - testScope.runTest { - `when`(deviceItem1.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE) - - interactor.updateDeviceItemOnClick(deviceItem1) - - verify(cachedDevice1).connect() - verify(logger) - .logDeviceClick(cachedDevice1.address, DeviceItemType.SAVED_BLUETOOTH_DEVICE) - } - } - private fun createFactory( isFilterMatchFunc: (CachedBluetoothDevice) -> Boolean, deviceItem: DeviceItem diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt new file mode 100644 index 000000000000..9a3667819ec7 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.ui.composable + +import android.app.AlertDialog +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.doubleClick +import androidx.compose.ui.test.hasTestTag +import androidx.compose.ui.test.performTouchInput +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import com.android.compose.theme.PlatformTheme +import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel +import com.android.systemui.bouncer.ui.BouncerDialogFactory +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout +import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.motion.createSysUiComposeMotionTestRule +import com.android.systemui.scene.domain.interactor.sceneContainerStartable +import com.android.systemui.testKosmos +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.motion.compose.ComposeFeatureCaptures.alpha +import platform.test.motion.compose.ComposeFeatureCaptures.positionInRoot +import platform.test.motion.compose.ComposeRecordingSpec +import platform.test.motion.compose.MotionControl +import platform.test.motion.compose.feature +import platform.test.motion.compose.motionTestValueOfNode +import platform.test.motion.compose.recordMotion +import platform.test.motion.compose.runTest +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.Displays.FoldableInner + +@RunWith(AndroidJUnit4::class) +@LargeTest +class BouncerContentTest : SysuiTestCase() { + private val deviceSpec = DeviceEmulationSpec(FoldableInner) + private val kosmos = testKosmos() + + @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos, deviceSpec) + + private val bouncerDialogFactory = + object : BouncerDialogFactory { + override fun invoke(): AlertDialog { + throw AssertionError() + } + } + + @Before + fun setUp() { + kosmos.sceneContainerStartable.start() + kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) + } + + @Composable + private fun BouncerContentUnderTest() { + PlatformTheme { + BouncerContent( + viewModel = kosmos.bouncerViewModel, + layout = BouncerSceneLayout.BESIDE_USER_SWITCHER, + modifier = Modifier.fillMaxSize().testTag("BouncerContent"), + dialogFactory = bouncerDialogFactory + ) + } + } + + @Test + fun doubleClick_swapSide() = + motionTestRule.runTest { + val motion = + recordMotion( + content = { BouncerContentUnderTest() }, + ComposeRecordingSpec( + MotionControl { + onNode(hasTestTag("BouncerContent")).performTouchInput { + doubleClick(position = centerLeft) + } + + awaitCondition { + motionTestValueOfNode(BouncerMotionTestKeys.swapAnimationEnd) + } + } + ) { + feature(hasTestTag("UserSwitcher"), positionInRoot, "userSwitcher_pos") + feature(hasTestTag("UserSwitcher"), alpha, "userSwitcher_alpha") + feature(hasTestTag("FoldAware"), positionInRoot, "foldAware_pos") + feature(hasTestTag("FoldAware"), alpha, "foldAware_alpha") + } + ) + + assertThat(motion).timeSeriesMatchesGolden() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt new file mode 100644 index 000000000000..4f6f98e8fdb0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bouncer.ui.composable + +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.domain.interactor.bouncerInteractor +import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel +import com.android.systemui.kosmos.testScope +import com.android.systemui.motion.createSysUiComposeMotionTestRule +import com.android.systemui.testKosmos +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.takeWhile +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.motion.compose.ComposeRecordingSpec +import platform.test.motion.compose.MotionControl +import platform.test.motion.compose.feature +import platform.test.motion.compose.motionTestValueOfNode +import platform.test.motion.compose.recordMotion +import platform.test.motion.compose.runTest +import platform.test.motion.golden.DataPointTypes + +@RunWith(AndroidJUnit4::class) +@LargeTest +class PatternBouncerTest : SysuiTestCase() { + private val kosmos = testKosmos() + + @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos) + + private val bouncerInteractor by lazy { kosmos.bouncerInteractor } + private val viewModel by lazy { + PatternBouncerViewModel( + applicationContext = context, + viewModelScope = kosmos.testScope.backgroundScope, + interactor = bouncerInteractor, + isInputEnabled = MutableStateFlow(true).asStateFlow(), + onIntentionalUserInput = {}, + ) + } + + @Composable + private fun PatternBouncerUnderTest() { + PatternBouncer(viewModel, centerDotsVertically = true, modifier = Modifier.size(400.dp)) + } + + @Test + fun entryAnimation() = + motionTestRule.runTest { + val motion = + recordMotion( + content = { play -> if (play) PatternBouncerUnderTest() }, + ComposeRecordingSpec.until( + recordBefore = false, + checkDone = { motionTestValueOfNode(MotionTestKeys.entryCompleted) } + ) { + feature(MotionTestKeys.dotAppearFadeIn, floatArray) + feature(MotionTestKeys.dotAppearMoveUp, floatArray) + } + ) + + assertThat(motion).timeSeriesMatchesGolden() + } + + @Test + fun animateFailure() = + motionTestRule.runTest { + val failureAnimationMotionControl = + MotionControl( + delayReadyToPlay = { + // Skip entry animation. + awaitCondition { motionTestValueOfNode(MotionTestKeys.entryCompleted) } + }, + delayRecording = { + // Trigger failure animation by calling onDragEnd without having recorded a + // pattern before. + viewModel.onDragEnd() + // Failure animation starts when animateFailure flips to true... + viewModel.animateFailure.takeWhile { !it }.collect {} + } + ) { + // ... and ends when the composable flips it back to false. + viewModel.animateFailure.takeWhile { it }.collect {} + } + + val motion = + recordMotion( + content = { PatternBouncerUnderTest() }, + ComposeRecordingSpec(failureAnimationMotionControl) { + feature(MotionTestKeys.dotScaling, floatArray) + } + ) + assertThat(motion).timeSeriesMatchesGolden() + } + + companion object { + val floatArray = DataPointTypes.listOf(DataPointTypes.float) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java index bc6c459881be..5361cef2ac64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java @@ -35,9 +35,13 @@ import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; import com.android.systemui.communal.domain.interactor.CommunalInteractor; +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerFake; +import com.android.systemui.flags.DisableSceneContainer; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -50,6 +54,7 @@ import com.android.systemui.util.sensors.ProximitySensor; import com.android.systemui.util.sensors.ThresholdSensor; import com.android.systemui.util.time.FakeSystemClock; +import kotlinx.coroutines.flow.MutableStateFlow; import kotlinx.coroutines.flow.StateFlowKt; import org.junit.Before; @@ -89,6 +94,14 @@ public class FalsingCollectorImplTest extends SysuiTestCase { private SelectedUserInteractor mSelectedUserInteractor; @Mock private CommunalInteractor mCommunalInteractor; + @Mock + private DeviceEntryInteractor mDeviceEntryInteractor; + private final MutableStateFlow<Boolean> mIsDeviceEntered = + StateFlowKt.MutableStateFlow(false); + @Mock + private SceneContainerOcclusionInteractor mSceneContainerOcclusionInteractor; + private final MutableStateFlow<Boolean> mIsInvisibleDueToOcclusion = + StateFlowKt.MutableStateFlow(false); private final DockManagerFake mDockManager = new DockManagerFake(); private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mFakeExecutor = new FakeExecutor(mFakeSystemClock); @@ -99,15 +112,21 @@ public class FalsingCollectorImplTest extends SysuiTestCase { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(false); when(mShadeInteractor.isQsExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false)); + when(mDeviceEntryInteractor.isDeviceEntered()).thenReturn(mIsDeviceEntered); + when(mSceneContainerOcclusionInteractor.getInvisibleDueToOcclusion()).thenReturn( + mIsInvisibleDueToOcclusion); + mFalsingCollector = new FalsingCollectorImpl(mFalsingDataProvider, mFalsingManager, mKeyguardUpdateMonitor, mHistoryTracker, mProximitySensor, mStatusBarStateController, mKeyguardStateController, () -> mShadeInteractor, mBatteryController, mDockManager, mFakeExecutor, mJavaAdapter, mFakeSystemClock, () -> mSelectedUserInteractor, - () -> mCommunalInteractor + () -> mCommunalInteractor, () -> mDeviceEntryInteractor, + () -> mSceneContainerOcclusionInteractor ); mFalsingCollector.init(); } @@ -189,7 +208,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test - public void testRegisterSensor_OccludingActivity() { + @DisableSceneContainer + public void testRegisterSensor_OccludingActivity_sceneContainerDisabled() { when(mKeyguardStateController.isOccluded()).thenReturn(true); ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor = @@ -203,6 +223,21 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test + @EnableSceneContainer + public void testRegisterSensor_OccludingActivity_sceneContainerEnabled() { + mIsInvisibleDueToOcclusion.setValue(true); + + ArgumentCaptor<StatusBarStateController.StateListener> stateListenerArgumentCaptor = + ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); + verify(mStatusBarStateController).addCallback(stateListenerArgumentCaptor.capture()); + + mFalsingCollector.onScreenTurningOn(); + reset(mProximitySensor); + stateListenerArgumentCaptor.getValue().onStateChanged(StatusBarState.SHADE); + verify(mProximitySensor).register(any(ThresholdSensor.Listener.class)); + } + + @Test public void testPassThroughEnterKeyEvent() { KeyEvent enterDown = KeyEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 0, 0, 0, 0, 0, ""); @@ -280,7 +315,8 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test - public void testAvoidUnlocked() { + @DisableSceneContainer + public void testAvoidUnlocked_sceneContainerDisabled() { MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); @@ -296,6 +332,23 @@ public class FalsingCollectorImplTest extends SysuiTestCase { } @Test + @EnableSceneContainer + public void testAvoidUnlocked_sceneContainerEnabled() { + MotionEvent down = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); + MotionEvent up = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + + mIsDeviceEntered.setValue(true); + + // Nothing passed initially + mFalsingCollector.onTouchEvent(down); + verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); + + // Up event would normally flush the up event, but doesn't. + mFalsingCollector.onTouchEvent(up); + verify(mFalsingDataProvider, never()).onMotionEvent(any(MotionEvent.class)); + } + + @Test public void testGestureWhenDozing() { // We check the FalsingManager for taps during the transition to AoD (dozing=true, // pulsing=false), so the FalsingCollector needs to continue to analyze events that occur 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/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt deleted file mode 100644 index ab034652e0cc..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.systemui.contrast - -import android.app.UiModeManager -import android.app.UiModeManager.ContrastUtils.fromContrastLevel -import android.os.Looper -import android.provider.Settings -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper.RunWithLooper -import android.view.LayoutInflater -import android.view.View -import android.widget.FrameLayout -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.DialogTransitionAnimator -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.FeatureFlags -import com.android.systemui.model.SysUiState -import com.android.systemui.settings.UserTracker -import com.android.systemui.statusbar.phone.SystemUIDialog -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.settings.SecureSettings -import com.android.systemui.util.time.FakeSystemClock -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mock -import org.mockito.Mockito.eq -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -/** Test the behaviour of buttons of the [ContrastDialogDelegate]. */ -@SmallTest -@RunWith(AndroidTestingRunner::class) -@RunWithLooper -class ContrastDialogDelegateTest : SysuiTestCase() { - - private val mainExecutor = FakeExecutor(FakeSystemClock()) - private lateinit var mContrastDialogDelegate: ContrastDialogDelegate - @Mock private lateinit var sysuiDialogFactory: SystemUIDialog.Factory - @Mock private lateinit var sysuiDialog: SystemUIDialog - @Mock private lateinit var mockUiModeManager: UiModeManager - @Mock private lateinit var mockUserTracker: UserTracker - @Mock private lateinit var mockSecureSettings: SecureSettings - @Mock private lateinit var sysuiState: SysUiState - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - mDependency.injectTestDependency(FeatureFlags::class.java, FakeFeatureFlags()) - mDependency.injectTestDependency(SysUiState::class.java, sysuiState) - mDependency.injectMockDependency(DialogTransitionAnimator::class.java) - whenever(sysuiState.setFlag(any(), any())).thenReturn(sysuiState) - whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java))) - .thenReturn(sysuiDialog) - whenever(sysuiDialog.layoutInflater).thenReturn(LayoutInflater.from(mContext)) - - whenever(mockUserTracker.userId).thenReturn(context.userId) - if (Looper.myLooper() == null) Looper.prepare() - - mContrastDialogDelegate = - ContrastDialogDelegate( - sysuiDialogFactory, - mainExecutor, - mockUiModeManager, - mockUserTracker, - mockSecureSettings - ) - - mContrastDialogDelegate.createDialog() - val viewCaptor = ArgumentCaptor.forClass(View::class.java) - verify(sysuiDialog).setView(viewCaptor.capture()) - whenever(sysuiDialog.requireViewById(anyInt()) as View?).then { - viewCaptor.value.requireViewById(it.getArgument(0)) - } - } - - @Test - fun testClickButtons_putsContrastInSettings() { - mContrastDialogDelegate.onCreate(sysuiDialog, null) - - mContrastDialogDelegate.contrastButtons.forEach { - (contrastLevel: Int, clickedButton: FrameLayout) -> - clickedButton.performClick() - mainExecutor.runAllReady() - verify(mockSecureSettings) - .putFloatForUser( - eq(Settings.Secure.CONTRAST_LEVEL), - eq(fromContrastLevel(contrastLevel)), - eq(context.userId) - ) - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt index 43c860cd302f..2b7e7adbe022 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt @@ -29,7 +29,7 @@ import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope @@ -162,7 +162,7 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { kosmos.fakeKeyguardRepository.setBiometricUnlockSource( BiometricUnlockSource.FINGERPRINT_SENSOR ) - kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK) } private fun fingerprintFailure() { 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/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt new file mode 100644 index 000000000000..05a2ca20037b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.ui + +import android.content.Intent +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyboard.shortcut.fakeShortcutHelperStartActivity +import com.android.systemui.keyboard.shortcut.shortcutHelperActivityStarter +import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper +import com.android.systemui.keyboard.shortcut.ui.view.ShortcutHelperActivity +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class ShortcutHelperActivityStarterTest : SysuiTestCase() { + + private val kosmos = + Kosmos().also { + it.testCase = this + it.testDispatcher = UnconfinedTestDispatcher() + } + + private val testScope = kosmos.testScope + private val testHelper = kosmos.shortcutHelperTestHelper + private val fakeStartActivity = kosmos.fakeShortcutHelperStartActivity + private val starter = kosmos.shortcutHelperActivityStarter + + @Test + fun start_doesNotStartByDefault() = + testScope.runTest { + starter.start() + + assertThat(fakeStartActivity.startIntents).isEmpty() + } + + @Test + fun start_onToggle_startsActivity() = + testScope.runTest { + starter.start() + + testHelper.toggle(deviceId = 456) + + verifyShortcutHelperActivityStarted() + } + + @Test + fun start_onToggle_multipleTimesStartsActivityOnlyWhenNotStarted() = + testScope.runTest { + starter.start() + + testHelper.toggle(deviceId = 456) + testHelper.toggle(deviceId = 456) + testHelper.toggle(deviceId = 456) + testHelper.toggle(deviceId = 456) + + verifyShortcutHelperActivityStarted(numTimes = 2) + } + + @Test + fun start_onRequestShowShortcuts_startsActivity() = + testScope.runTest { + starter.start() + + testHelper.showFromActivity() + + verifyShortcutHelperActivityStarted() + } + + @Test + fun start_onRequestShowShortcuts_multipleTimes_startsActivityOnlyOnce() = + testScope.runTest { + starter.start() + + testHelper.showFromActivity() + testHelper.showFromActivity() + testHelper.showFromActivity() + + verifyShortcutHelperActivityStarted(numTimes = 1) + } + + @Test + fun start_onRequestShowShortcuts_multipleTimes_startsActivityOnlyWhenNotStarted() = + testScope.runTest { + starter.start() + + testHelper.hideFromActivity() + testHelper.hideForSystem() + testHelper.toggle(deviceId = 987) + testHelper.showFromActivity() + testHelper.hideFromActivity() + testHelper.hideForSystem() + testHelper.toggle(deviceId = 456) + testHelper.showFromActivity() + + verifyShortcutHelperActivityStarted(numTimes = 2) + } + + private fun verifyShortcutHelperActivityStarted(numTimes: Int = 1) { + assertThat(fakeStartActivity.startIntents).hasSize(numTimes) + fakeStartActivity.startIntents.forEach { intent -> + assertThat(intent.flags).isEqualTo(Intent.FLAG_ACTIVITY_NEW_TASK) + assertThat(intent.filterEquals(Intent(context, ShortcutHelperActivity::class.java))) + .isTrue() + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt new file mode 100644 index 000000000000..44a8904f50da --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.ui.viewmodel + +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.keyboard.shortcut.shortcutHelperTestHelper +import com.android.systemui.keyboard.shortcut.shortcutHelperViewModel +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.model.sysUiState +import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class ShortcutHelperViewModelTest : SysuiTestCase() { + + private val kosmos = + Kosmos().also { + it.testCase = this + it.testDispatcher = UnconfinedTestDispatcher() + } + + private val testScope = kosmos.testScope + private val testHelper = kosmos.shortcutHelperTestHelper + private val sysUiState = kosmos.sysUiState + private val viewModel = kosmos.shortcutHelperViewModel + + @Test + fun shouldShow_falseByDefault() = + testScope.runTest { + val shouldShow by collectLastValue(viewModel.shouldShow) + + assertThat(shouldShow).isFalse() + } + + @Test + fun shouldShow_trueAfterShowRequested() = + testScope.runTest { + val shouldShow by collectLastValue(viewModel.shouldShow) + + testHelper.showFromActivity() + + assertThat(shouldShow).isTrue() + } + + @Test + fun shouldShow_trueAfterToggleRequested() = + testScope.runTest { + val shouldShow by collectLastValue(viewModel.shouldShow) + + testHelper.toggle(deviceId = 123) + + assertThat(shouldShow).isTrue() + } + + @Test + fun shouldShow_falseAfterToggleTwice() = + testScope.runTest { + val shouldShow by collectLastValue(viewModel.shouldShow) + + testHelper.toggle(deviceId = 123) + testHelper.toggle(deviceId = 123) + + assertThat(shouldShow).isFalse() + } + + @Test + fun shouldShow_falseAfterViewClosed() = + testScope.runTest { + val shouldShow by collectLastValue(viewModel.shouldShow) + + testHelper.toggle(deviceId = 567) + viewModel.onViewClosed() + + assertThat(shouldShow).isFalse() + } + + @Test + fun shouldShow_doesNotEmitDuplicateValues() = + testScope.runTest { + val shouldShowValues by collectValues(viewModel.shouldShow) + + testHelper.hideForSystem() + testHelper.toggle(deviceId = 987) + testHelper.showFromActivity() + viewModel.onViewClosed() + testHelper.hideFromActivity() + testHelper.hideForSystem() + testHelper.toggle(deviceId = 456) + testHelper.showFromActivity() + + assertThat(shouldShowValues).containsExactly(false, true, false, true).inOrder() + } + + @Test + fun shouldShow_emitsLatestValueToNewSubscribers() = + testScope.runTest { + val shouldShow by collectLastValue(viewModel.shouldShow) + + testHelper.showFromActivity() + + val shouldShowNew by collectLastValue(viewModel.shouldShow) + assertThat(shouldShowNew).isEqualTo(shouldShow) + } + + @Test + fun sysUiStateFlag_disabledByDefault() = + testScope.runTest { + assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isFalse() + } + + @Test + fun sysUiStateFlag_trueAfterViewOpened() = + testScope.runTest { + viewModel.onViewOpened() + + assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isTrue() + } + + @Test + fun sysUiStateFlag_falseAfterViewClosed() = + testScope.runTest { + viewModel.onViewOpened() + viewModel.onViewClosed() + + assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_SHORTCUT_HELPER_SHOWING)).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java index 39fcd417ec7a..5b836b6b06b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java @@ -346,6 +346,24 @@ public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCas } @Test + public void testStartDozing_withMinShowTime() { + // GIVEN a biometric message is showing + mController.updateIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE, + new KeyguardIndication.Builder() + .setMessage("test_message") + .setMinVisibilityMillis(5000L) + .setTextColor(ColorStateList.valueOf(Color.WHITE)) + .build(), + true); + + // WHEN the device wants to hide the biometric message + mController.hideIndication(INDICATION_TYPE_BIOMETRIC_MESSAGE); + + // THEN switch to INDICATION_TYPE_NONE + verify(mView).switchIndication(null); + } + + @Test public void testStoppedDozing() { // GIVEN we're dozing & we have an indication message mStatusBarStateListener.onDozingChanged(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 325e7bf31a43..6b1d39a6b278 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -241,7 +241,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { .thenReturn(mock(Flow.class)); when(mDreamViewModel.getTransitionEnded()) .thenReturn(mock(Flow.class)); - when(mCommunalTransitionViewModel.getShowByDefault()) + when(mCommunalTransitionViewModel.getShowCommunalFromOccluded()) .thenReturn(mock(Flow.class)); when(mCommunalTransitionViewModel.getTransitionFromOccludedEnded()) .thenReturn(mock(Flow.class)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index b50d248d6940..977116e812ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -8,6 +8,8 @@ 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.flags.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -19,6 +21,10 @@ import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.utils.GlobalWindowManager @@ -69,12 +75,13 @@ class ResourceTrimmerTest : SysuiTestCase() { resourceTrimmer = ResourceTrimmer( keyguardInteractor, - powerInteractor, - kosmos.keyguardTransitionInteractor, - globalWindowManager, - testScope.backgroundScope, - kosmos.testDispatcher, - featureFlags + powerInteractor = powerInteractor, + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, + globalWindowManager = globalWindowManager, + applicationScope = testScope.backgroundScope, + bgDispatcher = kosmos.testDispatcher, + featureFlags = featureFlags, + sceneInteractor = kosmos.sceneInteractor, ) resourceTrimmer.start() } @@ -203,6 +210,7 @@ class ResourceTrimmerTest : SysuiTestCase() { @Test @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) + @DisableSceneContainer fun keyguardTransitionsToGone_trimsFontCache() = testScope.runTest { keyguardTransitionRepository.sendTransitionSteps( @@ -218,6 +226,20 @@ class ResourceTrimmerTest : SysuiTestCase() { @Test @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) + @EnableSceneContainer + fun keyguardTransitionsToGone_trimsFontCache_scene_container() = + testScope.runTest { + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + verify(globalWindowManager, times(1)) + .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT) + verifyNoMoreInteractions(globalWindowManager) + } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) + @DisableSceneContainer fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() = testScope.runTest { featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false) @@ -231,4 +253,18 @@ class ResourceTrimmerTest : SysuiTestCase() { .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) verify(globalWindowManager, times(0)).trimCaches(any()) } + + @Test + @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) + @EnableSceneContainer + fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache_scene_container() = + testScope.runTest { + featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false) + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + // Memory hidden should still be called. + verify(globalWindowManager, times(1)) + .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) + verify(globalWindowManager, times(0)).trimCaches(any()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt index bcaad01f1a24..f5b5261de139 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt @@ -19,24 +19,20 @@ package com.android.systemui.keyguard.data.repository -import android.os.fakeExecutorHandler import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.ConfigurationRepository -import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint -import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT +import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.android.systemui.util.ThreadAssert -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat 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 @@ -50,31 +46,32 @@ import org.mockito.MockitoAnnotations class KeyguardBlueprintRepositoryTest : SysuiTestCase() { private lateinit var underTest: KeyguardBlueprintRepository @Mock lateinit var configurationRepository: ConfigurationRepository - @Mock lateinit var defaultLockscreenBlueprint: DefaultKeyguardBlueprint @Mock lateinit var threadAssert: ThreadAssert + private val testScope = TestScope(StandardTestDispatcher()) private val kosmos: Kosmos = testKosmos() @Before fun setup() { MockitoAnnotations.initMocks(this) - with(kosmos) { - whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT) - underTest = - KeyguardBlueprintRepository( - setOf(defaultLockscreenBlueprint), - fakeExecutorHandler, - threadAssert, - ) - } + underTest = kosmos.keyguardBlueprintRepository } @Test fun testApplyBlueprint_DefaultLayout() { testScope.runTest { val blueprint by collectLastValue(underTest.blueprint) - underTest.applyBlueprint(defaultLockscreenBlueprint) - assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint) + underTest.applyBlueprint(DefaultKeyguardBlueprint.DEFAULT) + assertThat(blueprint).isEqualTo(kosmos.defaultKeyguardBlueprint) + } + } + + @Test + fun testApplyBlueprint_SplitShadeLayout() { + testScope.runTest { + val blueprint by collectLastValue(underTest.blueprint) + underTest.applyBlueprint(SplitShadeKeyguardBlueprint.ID) + assertThat(blueprint).isEqualTo(kosmos.splitShadeBlueprint) } } @@ -83,33 +80,22 @@ class KeyguardBlueprintRepositoryTest : SysuiTestCase() { testScope.runTest { val blueprint by collectLastValue(underTest.blueprint) underTest.refreshBlueprint() - assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint) + assertThat(blueprint).isEqualTo(kosmos.defaultKeyguardBlueprint) } } @Test - fun testTransitionToLayout_validId() { - assertThat(underTest.applyBlueprint(DEFAULT)).isTrue() + fun testTransitionToDefaultLayout_validId() { + assertThat(underTest.applyBlueprint(DefaultKeyguardBlueprint.DEFAULT)).isTrue() } @Test - fun testTransitionToLayout_invalidId() { - assertThat(underTest.applyBlueprint("abc")).isFalse() + fun testTransitionToSplitShadeLayout_validId() { + assertThat(underTest.applyBlueprint(SplitShadeKeyguardBlueprint.ID)).isTrue() } @Test - fun testTransitionToSameBlueprint_refreshesBlueprint() = - with(kosmos) { - testScope.runTest { - val transition by collectLastValue(underTest.refreshTransition) - fakeExecutor.runAllReady() - runCurrent() - - underTest.applyBlueprint(defaultLockscreenBlueprint) - fakeExecutor.runAllReady() - runCurrent() - - assertThat(transition).isNotNull() - } - } + fun testTransitionToLayout_invalidId() { + assertThat(underTest.applyBlueprint("abc")).isFalse() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt index 53560d740575..48a5df91d47c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt @@ -25,16 +25,19 @@ import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.app.animation.Interpolators import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN +import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.KeyguardState.OFF 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.keyguard.util.KeyguardTransitionRunner +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -45,6 +48,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.dropWhile import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After @@ -372,6 +377,43 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() { assertThat(wtfHandler.failed).isTrue() } + @Test + fun simulateRaceConditionIsProcessedInOrder() = + testScope.runTest { + val ktr = KeyguardTransitionRepositoryImpl(kosmos.testDispatcher) + val steps by collectValues(ktr.transitions.dropWhile { step -> step.from == OFF }) + + // Add a delay to the first transition in order to attempt to have the second transition + // be processed first + val info1 = TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null) + launch { + ktr.forceDelayForRaceConditionTest = true + ktr.startTransition(info1) + } + val info2 = TransitionInfo(OWNER_NAME, LOCKSCREEN, OCCLUDED, animator = null) + launch { + ktr.forceDelayForRaceConditionTest = false + ktr.startTransition(info2) + } + + runCurrent() + assertThat(steps.isEmpty()).isTrue() + + advanceTimeBy(60L) + assertThat(steps[0]) + .isEqualTo( + TransitionStep(info1.from, info1.to, 0f, TransitionState.STARTED, OWNER_NAME) + ) + assertThat(steps[1]) + .isEqualTo( + TransitionStep(info1.from, info1.to, 0f, TransitionState.CANCELED, OWNER_NAME) + ) + assertThat(steps[2]) + .isEqualTo( + TransitionStep(info2.from, info2.to, 0f, TransitionState.STARTED, OWNER_NAME) + ) + } + private fun listWithStep( step: BigDecimal, start: BigDecimal = BigDecimal.ZERO, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt index cded2a4ec27c..593cfde2a503 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt @@ -45,8 +45,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository -import com.android.systemui.keyguard.data.repository.keyguardRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +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.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat @@ -99,7 +98,7 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { to = KeyguardState.DOZING, testScope ) - kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE) + kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE) reset(transitionRepository) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt index 8435a03fae9a..7c92ede89541 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt @@ -42,8 +42,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository -import com.android.systemui.keyguard.data.repository.keyguardRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.util.KeyguardTransitionRepositorySpySubject.Companion.assertThat import com.android.systemui.kosmos.testScope @@ -87,7 +86,7 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() { to = KeyguardState.DREAMING, testScope ) - kosmos.keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.NONE) + kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE) reset(transitionRepository) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt index ac5823e9365b..0bdf47a51670 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.biometrics.shared.model.SensorStrength import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository +import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.kosmos.testScope @@ -40,6 +41,7 @@ import com.android.systemui.testKosmos import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -54,7 +56,7 @@ import org.mockito.MockitoAnnotations class KeyguardBlueprintInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val underTest by lazy { kosmos.keyguardBlueprintInteractor } + private val underTest = kosmos.keyguardBlueprintInteractor private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository } private val configurationRepository by lazy { kosmos.fakeConfigurationRepository } private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository } @@ -79,8 +81,9 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { val blueprintId by collectLastValue(underTest.blueprintId) kosmos.shadeRepository.setShadeMode(ShadeMode.Single) configurationRepository.onConfigurationChange() - runCurrent() + runCurrent() + advanceUntilIdle() assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.Companion.DEFAULT) } } @@ -92,8 +95,9 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { val blueprintId by collectLastValue(underTest.blueprintId) kosmos.shadeRepository.setShadeMode(ShadeMode.Split) configurationRepository.onConfigurationChange() - runCurrent() + runCurrent() + advanceUntilIdle() assertThat(blueprintId).isEqualTo(SplitShadeKeyguardBlueprint.Companion.ID) } } @@ -102,12 +106,13 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) fun fingerprintPropertyInitialized_updatesBlueprint() { testScope.runTest { - val blueprintId by collectLastValue(underTest.blueprintId) - kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull() + fingerprintPropertyRepository.supportsUdfps() // initialize properties - runCurrent() - assertThat(blueprintId).isEqualTo(SplitShadeKeyguardBlueprint.Companion.ID) + runCurrent() + advanceUntilIdle() + assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull() } } @@ -119,9 +124,23 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { kosmos.shadeRepository.setShadeMode(ShadeMode.Split) clockRepository.setCurrentClock(clockController) configurationRepository.onConfigurationChange() - runCurrent() + runCurrent() + advanceUntilIdle() assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.DEFAULT) } } + + @Test + fun testRefreshFromConfigChange() { + testScope.runTest { + assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull() + + configurationRepository.onConfigurationChange() + + runCurrent() + advanceUntilIdle() + assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull() + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt index 62855d79c052..974e3bb133d8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt @@ -21,12 +21,18 @@ 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.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.DismissAction import com.android.systemui.keyguard.shared.model.KeyguardDone import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -65,10 +71,11 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { underTest = KeyguardDismissActionInteractor( - keyguardRepository, - kosmos.keyguardTransitionInteractor, - dismissInteractorWithDependencies.interactor, - testScope.backgroundScope, + repository = keyguardRepository, + transitionInteractor = kosmos.keyguardTransitionInteractor, + dismissInteractor = dismissInteractorWithDependencies.interactor, + applicationScope = testScope.backgroundScope, + sceneInteractor = kosmos.sceneInteractor, ) } @@ -153,6 +160,7 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() = testScope.runTest { val executeDismissAction by collectLastValue(underTest.executeDismissAction) @@ -179,6 +187,29 @@ class KeyguardDismissActionInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction_scene_container() = + testScope.runTest { + val executeDismissAction by collectLastValue(underTest.executeDismissAction) + + // WHEN a keyguard action will run after the keyguard is gone + val onDismissAction = {} + keyguardRepository.setDismissAction( + DismissAction.RunAfterKeyguardGone( + dismissAction = onDismissAction, + onCancelAction = {}, + message = "message", + willAnimateOnLockscreen = true, + ) + ) + assertThat(executeDismissAction).isNull() + + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + assertThat(executeDismissAction).isNotNull() + } + + @Test fun resetDismissAction() = testScope.runTest { val resetDismissAction by collectLastValue(underTest.resetDismissAction) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt index 9ccf2121b8d2..f32e7757328f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt @@ -274,4 +274,27 @@ class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() { runCurrent() assertThat(isAnimatingSurface).isFalse() } + + @Test + fun notificationLaunchFalse_isAnimatingSurfaceFalse() = + testScope.runTest { + val isAnimatingSurface by collectLastValue(underTest.isAnimatingSurface) + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + transitionState = TransitionState.STARTED, + ) + ) + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + transitionState = TransitionState.FINISHED, + ) + ) + kosmos.notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false) + runCurrent() + assertThat(isAnimatingSurface).isFalse() + } } 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 3827046e206d..fa3fe5c80adb 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 @@ -26,15 +28,18 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository 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.DockManager 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 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.DozeStateModel import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.KeyguardState @@ -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()) @@ -84,33 +91,52 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } private val testScope = kosmos.testScope - private val keyguardRepository = kosmos.fakeKeyguardRepository - private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository + private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } + private val bouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository } private var commandQueue = kosmos.fakeCommandQueue - private val shadeRepository = kosmos.fakeShadeRepository - private val transitionRepository = kosmos.fakeKeyguardTransitionRepository + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + private val transitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } private lateinit var featureFlags: FakeFeatureFlags // Used to verify transition requests for test output @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel - private val fromLockscreenTransitionInteractor = kosmos.fromLockscreenTransitionInteractor - private val fromDreamingTransitionInteractor = kosmos.fromDreamingTransitionInteractor - private val fromDozingTransitionInteractor = kosmos.fromDozingTransitionInteractor - private val fromOccludedTransitionInteractor = kosmos.fromOccludedTransitionInteractor - private val fromGoneTransitionInteractor = kosmos.fromGoneTransitionInteractor - private val fromAodTransitionInteractor = kosmos.fromAodTransitionInteractor - private val fromAlternateBouncerTransitionInteractor = + private val fromLockscreenTransitionInteractor by lazy { + kosmos.fromLockscreenTransitionInteractor + } + private val fromDreamingTransitionInteractor by lazy { kosmos.fromDreamingTransitionInteractor } + private val fromDozingTransitionInteractor by lazy { kosmos.fromDozingTransitionInteractor } + private val fromOccludedTransitionInteractor by lazy { kosmos.fromOccludedTransitionInteractor } + private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor } + private val fromAodTransitionInteractor by lazy { kosmos.fromAodTransitionInteractor } + private val fromAlternateBouncerTransitionInteractor by lazy { kosmos.fromAlternateBouncerTransitionInteractor - private val fromPrimaryBouncerTransitionInteractor = + } + private val fromPrimaryBouncerTransitionInteractor by lazy { kosmos.fromPrimaryBouncerTransitionInteractor - private val fromDreamingLockscreenHostedTransitionInteractor = + } + private val fromDreamingLockscreenHostedTransitionInteractor by lazy { kosmos.fromDreamingLockscreenHostedTransitionInteractor - private val fromGlanceableHubTransitionInteractor = kosmos.fromGlanceableHubTransitionInteractor + } + private val fromGlanceableHubTransitionInteractor by lazy { + kosmos.fromGlanceableHubTransitionInteractor + } + + private val powerInteractor by lazy { kosmos.powerInteractor } + private val communalInteractor by lazy { kosmos.communalInteractor } + private val dockManager by lazy { kosmos.fakeDockManager } - private val powerInteractor = kosmos.powerInteractor - 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() { @@ -119,9 +145,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 +165,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun lockscreenToPrimaryBouncerViaBouncerShowingCall() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -210,6 +239,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToDreaming() = testScope.runTest { // GIVEN a device that is not dreaming or dozing @@ -238,6 +268,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToDreamingLockscreenHosted() = testScope.runTest { // GIVEN a device that is not dreaming or dozing @@ -348,6 +379,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun dreamingLockscreenHostedToGone() = testScope.runTest { // GIVEN a prior transition has run to DREAMING_LOCKSCREEN_HOSTED @@ -358,7 +390,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // WHEN biometrics succeeds with wake and unlock from dream mode keyguardRepository.setBiometricUnlockState( - BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM + BiometricUnlockMode.WAKE_AND_UNLOCK_FROM_DREAM ) runCurrent() @@ -374,6 +406,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun dreamingLockscreenHostedToPrimaryBouncer() = testScope.runTest { // GIVEN a device dreaming with lockscreen hosted dream and not dozing @@ -527,6 +560,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun dozingToGoneWithUnlock() = testScope.runTest { // GIVEN a prior transition has run to DOZING @@ -535,7 +569,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // WHEN biometrics succeeds with wake and unlock mode powerInteractor.setAwakeForTest() - keyguardRepository.setBiometricUnlockState(BiometricUnlockModel.WAKE_AND_UNLOCK) + keyguardRepository.setBiometricUnlockState(BiometricUnlockMode.WAKE_AND_UNLOCK) advanceTimeBy(60L) assertThat(transitionRepository) @@ -600,12 +634,13 @@ 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 keyguardRepository.setDreamingWithOverlay(true) runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) - runCurrent() + advanceTimeBy(60L) // WHEN the device wakes up without a keyguard keyguardRepository.setKeyguardShowing(false) @@ -656,6 +691,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun goneToDozing() = testScope.runTest { // GIVEN a device with AOD not available @@ -681,6 +717,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun goneToAod() = testScope.runTest { // GIVEN a device with AOD available @@ -706,6 +743,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun goneToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to GONE @@ -727,6 +765,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun goneToDreaming() = testScope.runTest { // GIVEN a device that is not dreaming or dozing @@ -755,6 +794,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun goneToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to GONE @@ -784,6 +824,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun alternateBouncerToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -897,6 +938,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun alternateBouncerToGone() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -959,6 +1001,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToAod() = testScope.runTest { // GIVEN aod available @@ -989,6 +1032,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToDozing() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1017,6 +1061,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1040,6 +1085,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1071,6 +1117,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToGlanceableHubWhileDreaming() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1106,6 +1153,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun primaryBouncerToDreamingLockscreenHosted() = testScope.runTest { // GIVEN device dreaming with the lockscreen hosted dream and not dozing @@ -1135,6 +1183,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun occludedToGone() = testScope.runTest { // GIVEN a device on lockscreen @@ -1165,6 +1214,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun occludedToLockscreen() = testScope.runTest { // GIVEN a device on lockscreen @@ -1193,6 +1243,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun occludedToGlanceableHub() = testScope.runTest { // GIVEN a device on lockscreen @@ -1229,23 +1280,23 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test - fun occludedToGlanceableHubWhenDocked() = + @BrokenWithSceneContainer(339465026) + fun occludedToGlanceableHubWhenInitiallyOnHub() = testScope.runTest { - // GIVEN a device on lockscreen + // GIVEN a device on lockscreen and communal is available keyguardRepository.setKeyguardShowing(true) + kosmos.setCommunalAvailable(true) runCurrent() - // GIVEN a prior transition has run to OCCLUDED + // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED) keyguardRepository.setKeyguardOccluded(true) runCurrent() - // GIVEN device is docked/communal is available - dockManager.setIsDocked(true) - dockManager.setDockEvent(DockManager.STATE_DOCKED) + // GIVEN on blank scene val idleTransitionState = MutableStateFlow<ObservableTransitionState>( - ObservableTransitionState.Idle(CommunalScenes.Communal) + ObservableTransitionState.Idle(CommunalScenes.Blank) ) communalInteractor.setTransitionState(idleTransitionState) runCurrent() @@ -1315,6 +1366,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun primaryBouncerToOccluded() = testScope.runTest { // GIVEN a prior transition has run to PRIMARY_BOUNCER @@ -1340,6 +1392,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun dozingToOccluded() = testScope.runTest { // GIVEN a prior transition has run to DOZING @@ -1365,6 +1418,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun dreamingToOccluded() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -1393,6 +1447,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 @@ -1446,6 +1533,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun dreamingToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to DREAMING @@ -1463,6 +1551,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, + currentScene = flowOf(targetScene), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -1484,6 +1573,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToOccluded() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1507,6 +1597,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun aodToOccluded() = testScope.runTest { // GIVEN a prior transition has run to AOD @@ -1530,6 +1621,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun aodToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to AOD @@ -1553,6 +1645,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToOccluded_fromCameraGesture() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1576,7 +1669,9 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // THEN a transition from DOZING => OCCLUDED should occur assertThat(transitionRepository) .startedTransition( - ownerName = "FromDozingTransitionInteractor", + ownerName = + "FromDozingTransitionInteractor" + + "(keyguardInteractor.onCameraLaunchDetected)", from = KeyguardState.DOZING, to = KeyguardState.OCCLUDED, animatorAssertion = { it.isNotNull() }, @@ -1586,6 +1681,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun lockscreenToPrimaryBouncerDragging() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1595,8 +1691,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 +1709,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 +1725,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun lockscreenToGlanceableHub() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1645,6 +1742,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, + currentScene = flowOf(targetScene), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -1685,6 +1783,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToLockscreen() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1701,6 +1800,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, + currentScene = flowOf(targetScene), progress = progress, isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), @@ -1738,6 +1838,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToDozing() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1759,6 +1860,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToPrimaryBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -1780,6 +1882,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToAlternateBouncer() = testScope.runTest { // GIVEN a prior transition has run to ALTERNATE_BOUNCER @@ -1801,6 +1904,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @BrokenWithSceneContainer(339465026) fun glanceableHubToOccluded() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1831,6 +1935,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToGone() = testScope.runTest { // GIVEN a prior transition has run to GLANCEABLE_HUB @@ -1852,6 +1957,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + @DisableSceneContainer fun glanceableHubToDreaming() = testScope.runTest { // GIVEN that we are dreaming and not dozing @@ -1874,6 +1980,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { ObservableTransitionState.Transition( fromScene = currentScene, toScene = targetScene, + currentScene = flowOf(targetScene), progress = flowOf(0f, 0.1f), isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), 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..2b8a644162c7 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,34 @@ 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.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.domain.interactor.authenticationInteractor +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +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.domain.interactor.sceneInteractor +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.flow.map 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 +71,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 +192,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 +253,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 +343,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 +410,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 +512,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 +614,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 +729,115 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { values ) } + + @Test + @EnableSceneContainer + fun lockscreenVisibility() = + testScope.runTest { + val isDeviceUnlocked by + collectLastValue( + kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked } + ) + assertThat(isDeviceUnlocked).isFalse() + + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + + val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility) + assertThat(lockscreenVisibility).isTrue() + + kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "") + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(lockscreenVisibility).isTrue() + + kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) + assertThat(isDeviceUnlocked).isTrue() + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(lockscreenVisibility).isFalse() + + kosmos.sceneInteractor.changeScene(Scenes.Shade, "") + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(lockscreenVisibility).isFalse() + + kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "") + assertThat(currentScene).isEqualTo(Scenes.QuickSettings) + assertThat(lockscreenVisibility).isFalse() + + kosmos.sceneInteractor.changeScene(Scenes.Shade, "") + assertThat(currentScene).isEqualTo(Scenes.Shade) + assertThat(lockscreenVisibility).isFalse() + + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(lockscreenVisibility).isFalse() + + kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "") + assertThat(currentScene).isEqualTo(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..8a5af09f52ed --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/scenetransition/LockscreenSceneTransitionInteractorTest.kt @@ -0,0 +1,1318 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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. + */ + @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/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt index 9b2db3e5316c..1f132989b169 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt @@ -21,6 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock @@ -41,10 +42,9 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { private lateinit var executor: FakeExecutor @Mock private lateinit var activityTaskManagerService: IActivityTaskManager - @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier + @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Before fun setUp() { @@ -57,6 +57,7 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() { activityTaskManagerService = activityTaskManagerService, keyguardStateController = keyguardStateController, keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator, + keyguardTransitionInteractor = keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt index dbf6a29073a6..8a0613f9b010 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt @@ -66,25 +66,19 @@ class KeyguardBlueprintCommandListenerTest : SysuiTestCase() { fun testHelp() { command().execute(pw, listOf("help")) verify(pw, atLeastOnce()).println(anyString()) - verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString()) + verify(keyguardBlueprintInteractor, never()).transitionOrRefreshBlueprint(anyString()) } @Test fun testBlank() { command().execute(pw, listOf()) verify(pw, atLeastOnce()).println(anyString()) - verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString()) + verify(keyguardBlueprintInteractor, never()).transitionOrRefreshBlueprint(anyString()) } @Test fun testValidArg() { command().execute(pw, listOf("fake")) - verify(keyguardBlueprintInteractor).transitionToBlueprint("fake") - } - - @Test - fun testValidArg_Int() { - command().execute(pw, listOf("1")) - verify(keyguardBlueprintInteractor).transitionToBlueprint(1) + verify(keyguardBlueprintInteractor).transitionOrRefreshBlueprint("fake") } } 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/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt index 1396b20a800d..391831a61579 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt @@ -18,15 +18,19 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.filters.SmallTest +import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository 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.statusbar.policy.keyguardStateController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -34,6 +38,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.kotlin.whenever @ExperimentalCoroutinesApi @RunWith(JUnit4::class) @@ -49,13 +54,35 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() = testScope.runTest { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) + givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( + stepToLockscreen(0f, TransitionState.STARTED), + stepToLockscreen(.4f), + stepToLockscreen(1f, TransitionState.FINISHED), + ), + testScope, + ) + assertThat(canShowAlternateBouncer).isTrue() + transitionRepository.sendTransitionSteps( + listOf( + stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), + stepFromLockscreenToAlternateBouncer(.4f), + stepFromLockscreenToAlternateBouncer(.6f), + ), + testScope, + ) + assertThat(canShowAlternateBouncer).isTrue() + assertThat(alternateBouncerWindowRequired).isTrue() + + transitionRepository.sendTransitionSteps( + listOf( stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.4f), + stepFromAlternateBouncer(.2f), stepFromAlternateBouncer(.6f), ), testScope, @@ -77,13 +104,21 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) + givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( - stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.4f), - stepFromAlternateBouncer(.6f), - stepFromAlternateBouncer(1f), + stepToLockscreen(0f, TransitionState.STARTED), + stepToLockscreen(.4f), + stepToLockscreen(1f, TransitionState.FINISHED), + ), + testScope, + ) + transitionRepository.sendTransitionSteps( + listOf( + stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), + stepFromLockscreenToAlternateBouncer(.4f), + stepFromLockscreenToAlternateBouncer(.6f), ), testScope, ) @@ -96,13 +131,23 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) + givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( + stepFromLockscreenToDozing(0f, TransitionState.STARTED), + stepFromLockscreenToDozing(.4f), + stepFromLockscreenToDozing(.6f), + stepFromLockscreenToDozing(1f, TransitionState.FINISHED), + ), + testScope, + ) + assertThat(alternateBouncerWindowRequired).isFalse() + transitionRepository.sendTransitionSteps( + listOf( stepFromDozingToLockscreen(0f, TransitionState.STARTED), stepFromDozingToLockscreen(.4f), stepFromDozingToLockscreen(.6f), - stepFromDozingToLockscreen(1f), ), testScope, ) @@ -115,19 +160,39 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) + givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsRearFps() transitionRepository.sendTransitionSteps( listOf( - stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.4f), - stepFromAlternateBouncer(.6f), - stepFromAlternateBouncer(1f), + stepToLockscreen(0f, TransitionState.STARTED), + stepToLockscreen(.4f), + stepToLockscreen(1f, TransitionState.FINISHED), + ), + testScope, + ) + transitionRepository.sendTransitionSteps( + listOf( + stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), + stepFromLockscreenToAlternateBouncer(.4f), + stepFromLockscreenToAlternateBouncer(.6f), ), testScope, ) assertThat(alternateBouncerWindowRequired).isFalse() } + private fun stepToLockscreen( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return step( + from = KeyguardState.GONE, + to = KeyguardState.LOCKSCREEN, + value = value, + transitionState = state, + ) + } + private fun stepFromAlternateBouncer( value: Float, state: TransitionState = TransitionState.RUNNING @@ -140,6 +205,18 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ) } + private fun stepFromLockscreenToAlternateBouncer( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return step( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = value, + transitionState = state, + ) + } + private fun stepFromDozingToLockscreen( value: Float, state: TransitionState = TransitionState.RUNNING @@ -152,6 +229,18 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ) } + private fun stepFromLockscreenToDozing( + value: Float, + state: TransitionState = TransitionState.RUNNING + ): TransitionStep { + return step( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.DOZING, + value = value, + transitionState = state, + ) + } + private fun step( from: KeyguardState, to: KeyguardState, @@ -166,4 +255,16 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ownerName = "AlternateBouncerViewModelTest" ) } + + /** + * Given the alternate bouncer parameters are set so that the alternate bouncer can show, aside + * from the fingerprint modality. + */ + private fun givenCanShowAlternateBouncer() { + kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(false) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) + whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) + whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false) + } } 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 deleted file mode 100644 index 4bb0d4781376..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.keyguard.ui.viewmodel - -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository -import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository -import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.keyguardRepository -import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel.Companion.UNLOCKED_DELAY_MS -import com.android.systemui.kosmos.testScope -import com.android.systemui.testKosmos -import com.google.common.truth.Truth.assertThat -import kotlin.test.Test -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.runner.RunWith - -@ExperimentalCoroutinesApi -@SmallTest -@RunWith(AndroidJUnit4::class) -class DeviceEntryIconViewModelTest : SysuiTestCase() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository - private lateinit var fingerprintAuthRepository: FakeDeviceEntryFingerprintAuthRepository - private lateinit var deviceEntryIconTransition: FakeDeviceEntryIconTransition - private lateinit var underTest: DeviceEntryIconViewModel - - @Before - fun setUp() { - keyguardRepository = kosmos.fakeKeyguardRepository - fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository - fingerprintAuthRepository = kosmos.fakeDeviceEntryFingerprintAuthRepository - deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition - underTest = kosmos.deviceEntryIconViewModel - } - - @Test - fun isLongPressEnabled_udfpsRunning() = - testScope.runTest { - val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled) - fingerprintPropertyRepository.supportsUdfps() - fingerprintAuthRepository.setIsRunning(true) - keyguardRepository.setKeyguardDismissible(false) - assertThat(isLongPressEnabled).isFalse() - } - - @Test - fun isLongPressEnabled_unlocked() = - testScope.runTest { - val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled) - fingerprintPropertyRepository.supportsUdfps() - keyguardRepository.setKeyguardDismissible(true) - advanceTimeBy(UNLOCKED_DELAY_MS * 2) // wait for unlocked delay - runCurrent() - assertThat(isLongPressEnabled).isTrue() - } - - @Test - fun isLongPressEnabled_lock() = - testScope.runTest { - val isLongPressEnabled by collectLastValue(underTest.isLongPressEnabled) - keyguardRepository.setKeyguardDismissible(false) - - // udfps supported - fingerprintPropertyRepository.supportsUdfps() - assertThat(isLongPressEnabled).isTrue() - - // udfps isn't supported - fingerprintPropertyRepository.supportsRearFps() - assertThat(isLongPressEnabled).isFalse() - } - - @Test - fun isVisible() = - testScope.runTest { - val isVisible by collectLastValue(underTest.isVisible) - deviceEntryIconTransitionAlpha(1f) - assertThat(isVisible).isTrue() - - deviceEntryIconTransitionAlpha(0f) - assertThat(isVisible).isFalse() - - deviceEntryIconTransitionAlpha(.5f) - assertThat(isVisible).isTrue() - } - - 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..40663ceb2ad2 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 @@ -23,6 +23,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.BrokenWithSceneContainer import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository import com.android.systemui.keyguard.data.repository.keyguardClockRepository @@ -53,10 +54,10 @@ 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 + val underTest by lazy { kosmos.keyguardClockViewModel } val res = context.resources @Mock lateinit var clockController: ClockController @@ -67,7 +68,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase( var faceConfig = ClockFaceConfig() init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -96,6 +97,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase( } @Test + @BrokenWithSceneContainer(339465026) fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() = testScope.runTest { val currentClockLayout by collectLastValue(underTest.currentClockLayout) @@ -110,6 +112,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase( } @Test + @BrokenWithSceneContainer(339465026) fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() = testScope.runTest { val currentClockLayout by collectLastValue(underTest.currentClockLayout) @@ -124,6 +127,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase( } @Test + @BrokenWithSceneContainer(339465026) fun currentClockLayout_singleShade_smallClock_smallClock() = testScope.runTest { val currentClockLayout by collectLastValue(underTest.currentClockLayout) @@ -193,6 +197,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase( } @Test + @BrokenWithSceneContainer(339465026) fun testClockSize_dynamicClockSize() = testScope.runTest { with(kosmos) { @@ -216,6 +221,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization?) : SysuiTestCase( } @Test + @BrokenWithSceneContainer(339465026) fun isLargeClockVisible_whenSmallClockSize_isFalse() = testScope.runTest { val value by collectLastValue(underTest.isLargeClockVisible) 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..5986f4a9a9aa 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 @@ -33,9 +33,6 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor @@ -50,6 +47,9 @@ import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever private const val KEY = "TEST_KEY" private const val KEY_ALT = "TEST_KEY_2" @@ -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..3372f06dec22 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 @@ -66,9 +66,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.tuner.TunerService import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After @@ -90,6 +87,9 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoSession import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.capture +import org.mockito.kotlin.eq import org.mockito.quality.Strictness private const val KEY = "KEY" @@ -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..caaa42fc364c 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 @@ -40,9 +40,6 @@ import com.android.systemui.media.controls.util.MediaUiEventLogger import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import java.util.concurrent.Executor @@ -60,6 +57,9 @@ import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever private const val KEY = "TEST_KEY" private const val KEY_ALT = "TEST_KEY_2" @@ -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..3bf4173cd7c7 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 @@ -71,10 +71,6 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.SbnBuilder import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.utils.os.FakeHandler @@ -101,6 +97,10 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoSession import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.capture +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever import org.mockito.quality.Strictness private const val KEY = "KEY" @@ -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..d2701dd0d3a3 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 @@ -51,7 +51,6 @@ import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManagerFacto import com.android.systemui.res.R import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After @@ -60,6 +59,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 @@ -71,6 +71,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.eq private const val KEY = "TEST_KEY" private const val KEY_OLD = "TEST_KEY_OLD" @@ -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..31a243591b60 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 @@ -27,7 +27,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.shared.model.MediaData import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import org.junit.After import org.junit.Before @@ -38,12 +37,13 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock -import org.mockito.Mockito.any import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.eq private const val PACKAGE = "PKG" private const val KEY = "TEST_KEY" @@ -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..6ca0bef17404 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 @@ -31,10 +31,6 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before @@ -52,6 +48,10 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.capture +import org.mockito.kotlin.eq +import org.mockito.kotlin.whenever private const val KEY = "KEY" private const val PACKAGE = "PKG" @@ -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..a89139b18bed 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,10 @@ 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.DisableSceneContainer +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.flags.Flags +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -52,15 +56,16 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager import com.android.systemui.qs.PageIndicator import com.android.systemui.res.R +import com.android.systemui.scene.data.repository.Idle +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.testKosmos import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.capture -import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SecureSettings @@ -74,12 +79,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 @@ -88,6 +95,9 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.capture +import org.mockito.kotlin.eq private val DATA = MediaTestUtils.emptyMediaData @@ -136,6 +146,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) @@ -145,29 +158,30 @@ class MediaCarouselControllerTest : SysuiTestCase() { testDispatcher = UnconfinedTestDispatcher() mediaCarouselController = MediaCarouselController( - context, - mediaControlPanelFactory, - visualStabilityProvider, - mediaHostStatesManager, - activityStarter, - clock, - kosmos.testDispatcher, - executor, - bgExecutor, - testDispatcher, - mediaDataManager, - configurationController, - falsingManager, - dumpManager, - logger, - debugLogger, - mediaFlags, - keyguardUpdateMonitor, - kosmos.keyguardTransitionInteractor, - globalSettings, - secureSettings, - kosmos.mediaCarouselViewModel, - mediaViewControllerFactory, + context = context, + mediaControlPanelFactory = mediaControlPanelFactory, + visualStabilityProvider = visualStabilityProvider, + mediaHostStatesManager = mediaHostStatesManager, + activityStarter = activityStarter, + systemClock = clock, + mainDispatcher = kosmos.testDispatcher, + executor = executor, + bgExecutor = bgExecutor, + backgroundDispatcher = testDispatcher, + mediaManager = mediaDataManager, + configurationController = configurationController, + falsingManager = falsingManager, + dumpManager = dumpManager, + logger = logger, + debugLogger = debugLogger, + mediaFlags = mediaFlags, + keyguardUpdateMonitor = keyguardUpdateMonitor, + keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor, + globalSettings = globalSettings, + secureSettings = secureSettings, + mediaCarouselViewModel = kosmos.mediaCarouselViewModel, + mediaViewControllerFactory = mediaViewControllerFactory, + sceneInteractor = kosmos.sceneInteractor, ) verify(configurationController).addCallback(capture(configListener)) verify(mediaDataManager).addListener(capture(listener)) @@ -182,10 +196,19 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(globalSettings) .registerContentObserver( eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)), - settingsObserverCaptor.capture() + capture(settingsObserverCaptor) ) } + @After + fun tearDown() { + Settings.Secure.putInt( + context.contentResolver, + Settings.Secure.MEDIA_CONTROLS_RESUME, + originalResumeSetting + ) + } + @Test fun testPlayerOrdering() { // Test values: key, data, last active time @@ -818,10 +841,12 @@ class MediaCarouselControllerTest : SysuiTestCase() { verify(mediaCarousel).visibility = View.VISIBLE } + @DisableSceneContainer @ExperimentalCoroutinesApi @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 @@ -840,10 +865,30 @@ class MediaCarouselControllerTest : SysuiTestCase() { job.cancel() } + @EnableSceneContainer + @ExperimentalCoroutinesApi + @Test + fun testKeyguardGone_showMediaCarousel_scene_container() = + kosmos.testScope.runTest { + kosmos.fakeFeatureFlagsClassic.set(Flags.MEDIA_RETAIN_RECOMMENDATIONS, false) + var updatedVisibility = false + mediaCarouselController.updateHostVisibility = { updatedVisibility = true } + mediaCarouselController.mediaCarousel = mediaCarousel + + val job = mediaCarouselController.listenForAnyStateToGoneKeyguardTransition(this) + kosmos.setSceneTransition(Idle(Scenes.Gone)) + + verify(mediaCarousel).visibility = View.VISIBLE + assertEquals(true, updatedVisibility) + + job.cancel() + } + @ExperimentalCoroutinesApi @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 +915,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 +1014,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..6d7976e6e51d 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 @@ -98,11 +98,6 @@ import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimat import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.concurrency.FakeExecutor -import com.android.systemui.util.mockito.KotlinArgumentCaptor -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.argumentCaptor -import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -125,6 +120,9 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.eq private const val KEY = "TEST_KEY" private const val PACKAGE = "PKG" @@ -247,8 +245,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Set up package manager mocks val icon = context.getDrawable(R.drawable.ic_android) whenever(packageManager.getApplicationIcon(anyString())).thenReturn(icon) - whenever(packageManager.getApplicationIcon(any(ApplicationInfo::class.java))) - .thenReturn(icon) + whenever(packageManager.getApplicationIcon(any<ApplicationInfo>())).thenReturn(icon) whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt())) .thenReturn(applicationInfo) whenever(packageManager.getApplicationLabel(any())).thenReturn(PACKAGE) @@ -644,7 +641,7 @@ public class MediaControlPanelTest : SysuiTestCase() { bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView).setImageDrawable(any(Drawable::class.java)) + verify(albumView).setImageDrawable(any<Drawable>()) } @Test @@ -657,7 +654,7 @@ public class MediaControlPanelTest : SysuiTestCase() { bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView).setImageDrawable(any(Drawable::class.java)) + verify(albumView).setImageDrawable(any<Drawable>()) } @Test @@ -675,12 +672,12 @@ public class MediaControlPanelTest : SysuiTestCase() { player.bindPlayer(state0, PACKAGE) bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView).setImageDrawable(any(Drawable::class.java)) + verify(albumView).setImageDrawable(any<Drawable>()) // Run Metadata update so that later states don't update val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockAnimator, times(2)).addListener(captor.capture()) - captor.value.onAnimationEnd(mockAnimator) + captor.lastValue.onAnimationEnd(mockAnimator) assertThat(titleText.getText()).isEqualTo(TITLE) assertThat(artistText.getText()).isEqualTo(ARTIST) @@ -696,13 +693,13 @@ public class MediaControlPanelTest : SysuiTestCase() { player.bindPlayer(state2, PACKAGE) bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView, times(2)).setImageDrawable(any(Drawable::class.java)) + verify(albumView, times(2)).setImageDrawable(any<Drawable>()) // Fourth binding to new image runs transition due to color scheme change player.bindPlayer(state3, PACKAGE) bgExecutor.runAllReady() mainExecutor.runAllReady() - verify(albumView, times(3)).setImageDrawable(any(Drawable::class.java)) + verify(albumView, times(3)).setImageDrawable(any<Drawable>()) } @Test @@ -974,7 +971,7 @@ public class MediaControlPanelTest : SysuiTestCase() { val captor = argumentCaptor<SeekBarObserver>() verify(seekBarData).observeForever(captor.capture()) - val seekBarObserver = captor.value!! + val seekBarObserver = captor.lastValue // Then the seekbar is set to animate assertThat(seekBarObserver.animationEnabled).isTrue() @@ -1086,27 +1083,19 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(mockAvd0.isRunning()).thenReturn(false) val captor = ArgumentCaptor.forClass(Animatable2.AnimationCallback::class.java) verify(mockAvd0, times(1)).registerAnimationCallback(captor.capture()) - verify(mockAvd1, never()) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd2, never()) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) + verify(mockAvd1, never()).registerAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd2, never()).registerAnimationCallback(any<Animatable2.AnimationCallback>()) captor.getValue().onAnimationEnd(mockAvd0) // Validate correct state was bound assertThat(actionPlayPause.contentDescription).isEqualTo("loading") assertThat(actionPlayPause.getBackground()).isNull() - verify(mockAvd0, times(1)) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd1, times(1)) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd2, times(1)) - .registerAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd0, times(1)) - .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd1, times(1)) - .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java)) - verify(mockAvd2, never()) - .unregisterAnimationCallback(any(Animatable2.AnimationCallback::class.java)) + verify(mockAvd0, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd1, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd2, times(1)).registerAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd0, times(1)).unregisterAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd1, times(1)).unregisterAnimationCallback(any<Animatable2.AnimationCallback>()) + verify(mockAvd2, never()).unregisterAnimationCallback(any<Animatable2.AnimationCallback>()) } @Test @@ -1118,7 +1107,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Capture animation handler val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockAnimator, times(2)).addListener(captor.capture()) - val handler = captor.value + val handler = captor.lastValue // Validate text views unchanged but animation started assertThat(titleText.getText()).isEqualTo("") @@ -1147,7 +1136,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Capture animation handler val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockAnimator, times(2)).addListener(captor.capture()) - val handler = captor.value + val handler = captor.lastValue // Validate text views unchanged but animation started assertThat(titleText.getText()).isEqualTo("") @@ -1179,7 +1168,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // Capture animation handler val captor = argumentCaptor<Animator.AnimatorListener>() verify(mockAnimator, times(2)).addListener(captor.capture()) - val handler = captor.value + val handler = captor.lastValue handler.onAnimationEnd(mockAnimator) assertThat(artistText.getText()).isEqualTo("ARTIST_0") @@ -1344,7 +1333,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 +1349,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 +1359,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 @@ -1774,10 +1764,9 @@ public class MediaControlPanelTest : SysuiTestCase() { player.attachPlayer(viewHolder) player.bindPlayer(mediaData, KEY) - val callback: () -> Unit = {} - val captor = KotlinArgumentCaptor(callback::class.java) + val captor = argumentCaptor<() -> Unit>() verify(seekBarViewModel).logSeek = captor.capture() - captor.value.invoke() + captor.lastValue.invoke() verify(logger).logSeek(anyInt(), eq(PACKAGE), eq(instanceId)) } @@ -1800,7 +1789,7 @@ public class MediaControlPanelTest : SysuiTestCase() { // THEN it sends the PendingIntent without dismissing keyguard first, // and does not use the Intent directly (see b/271845008) captor.value.onClick(viewHolder.player) - verify(pendingIntent).send(any(Bundle::class.java)) + verify(pendingIntent).send(any<Bundle>()) verify(pendingIntent, never()).getIntent() verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any()) } @@ -2218,8 +2207,8 @@ public class MediaControlPanelTest : SysuiTestCase() { mainExecutor.runAllReady() verify(recCardTitle).setTextColor(any<Int>()) - verify(recAppIconItem, times(3)).setImageDrawable(any(Drawable::class.java)) - verify(coverItem, times(3)).setImageDrawable(any(Drawable::class.java)) + verify(recAppIconItem, times(3)).setImageDrawable(any<Drawable>()) + verify(coverItem, times(3)).setImageDrawable(any<Drawable>()) verify(coverItem, times(3)).imageMatrix = any() } @@ -2546,7 +2535,7 @@ public class MediaControlPanelTest : SysuiTestCase() { seamless.callOnClick() // Then we send the pending intent as is, without modifying the original intent - verify(pendingIntent).send(any(Bundle::class.java)) + verify(pendingIntent).send(any<Bundle>()) verify(pendingIntent, never()).getIntent() } @@ -2578,13 +2567,16 @@ public class MediaControlPanelTest : SysuiTestCase() { return Icon.createWithBitmap(bmp) } - private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener = - withArgCaptor { - verify(seekBarViewModel).setScrubbingChangeListener(capture()) - } + private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener { + val captor = argumentCaptor<SeekBarViewModel.ScrubbingChangeListener>() + verify(seekBarViewModel).setScrubbingChangeListener(captor.capture()) + return captor.lastValue + } - private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener = withArgCaptor { - verify(seekBarViewModel).setEnabledChangeListener(capture()) + private fun getEnabledChangeListener(): SeekBarViewModel.EnabledChangeListener { + val captor = argumentCaptor<SeekBarViewModel.EnabledChangeListener>() + verify(seekBarViewModel).setEnabledChangeListener(captor.capture()) + return captor.lastValue } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 9bb21f020be8..9616f6106a04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -128,6 +128,7 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mContext, TEST_PACKAGE, mContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index 2e6388ae3914..16b00c0b18b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -129,6 +129,7 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase { mContext, TEST_PACKAGE, mContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java index 4eb00385f857..45ae50623612 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java @@ -199,6 +199,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, mPackageName, mContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -292,6 +293,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, null, mContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -333,6 +335,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, null, mSpyContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -588,6 +591,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, "", mSpyContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -621,6 +625,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, "", mSpyContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -667,6 +672,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, null, mSpyContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -693,6 +699,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, null, mSpyContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -972,6 +979,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, null, mSpyContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, @@ -1174,6 +1182,7 @@ public class MediaOutputControllerTest extends SysuiTestCase { mSpyContext, null, mSpyContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java index 3b6a88af1ee0..1e8fbeac05bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Intent; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import androidx.test.filters.SmallTest; @@ -62,7 +64,7 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, times(1)) - .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any()); + .createAndShow(eq(getContext().getPackageName()), eq(false), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, never()) .createAndShow(any(), anyBoolean(), any()); } @@ -74,7 +76,7 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, never()) - .createAndShow(any(), anyBoolean(), any(), any()); + .createAndShow(any(), anyBoolean(), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, never()) .createAndShow(any(), anyBoolean(), any()); } @@ -85,62 +87,62 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, never()) - .createAndShow(any(), anyBoolean(), any(), any()); + .createAndShow(any(), anyBoolean(), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, never()) .createAndShow(any(), anyBoolean(), any()); } @Test + @DisableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) public void launchMediaOutputBroadcastDialog_flagOff_broadcastDialogFactoryNotCalled() { - mSetFlagsRule.disableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING); Intent intent = new Intent( MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName()); mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, never()) - .createAndShow(any(), anyBoolean(), any(), any()); + .createAndShow(any(), anyBoolean(), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, never()) .createAndShow(any(), anyBoolean(), any()); } @Test + @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) public void launchMediaOutputBroadcastDialog_ExtraPackageName_BroadcastDialogFactoryCalled() { - mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING); Intent intent = new Intent( MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); intent.putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, getContext().getPackageName()); mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, never()) - .createAndShow(any(), anyBoolean(), any(), any()); + .createAndShow(any(), anyBoolean(), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, times(1)) .createAndShow(eq(getContext().getPackageName()), eq(true), any()); } @Test + @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) public void launchMediaOutputBroadcastDialog_WrongExtraKey_DialogBroadcastFactoryNotCalled() { - mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING); Intent intent = new Intent( MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); intent.putExtra("Wrong Package Name Key", getContext().getPackageName()); mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, never()) - .createAndShow(any(), anyBoolean(), any(), any()); + .createAndShow(any(), anyBoolean(), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, never()) .createAndShow(any(), anyBoolean(), any()); } @Test + @EnableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING) public void launchMediaOutputBroadcastDialog_NoExtra_BroadcastDialogFactoryNotCalled() { - mSetFlagsRule.enableFlags(Flags.FLAG_LEGACY_LE_AUDIO_SHARING); Intent intent = new Intent( MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_BROADCAST_DIALOG); mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, never()) - .createAndShow(any(), anyBoolean(), any(), any()); + .createAndShow(any(), anyBoolean(), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, never()) .createAndShow(any(), anyBoolean(), any()); } @@ -153,7 +155,7 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, never()) - .createAndShow(any(), anyBoolean(), any(), any()); + .createAndShow(any(), anyBoolean(), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, never()) .createAndShow(any(), anyBoolean(), any()); } @@ -164,7 +166,7 @@ public class MediaOutputDialogReceiverTest extends SysuiTestCase { mMediaOutputDialogReceiver.onReceive(getContext(), intent); verify(mMockMediaOutputDialogManager, never()) - .createAndShow(any(), anyBoolean(), any(), any()); + .createAndShow(any(), anyBoolean(), any(), any(), any()); verify(mMockMediaOutputBroadcastDialogManager, never()) .createAndShow(any(), anyBoolean(), any()); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index cdef9644efa9..92d0a72e300c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -142,6 +142,7 @@ public class MediaOutputDialogTest extends SysuiTestCase { mContext, TEST_PACKAGE, mContext.getUser(), + /* token */ null, mMediaSessionManager, mLocalBluetoothManager, mStarter, 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/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt index db275ec190ac..db36131b825e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt @@ -52,7 +52,7 @@ class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { val taskId = 123 val isLowResolution = false val snapshot = createTaskSnapshot() - val thumbnailData = ThumbnailData(snapshot) + val thumbnailData = ThumbnailData.fromSnapshot(snapshot) whenever(activityManager.getTaskThumbnail(taskId, isLowResolution)) .thenReturn(thumbnailData) @@ -74,7 +74,7 @@ class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() { fun captureThumbnail_thumbnailAvailable_returnsThumbnailData() = testScope.runTest { val taskId = 321 - val thumbnailData = ThumbnailData(createTaskSnapshot()) + val thumbnailData = ThumbnailData.fromSnapshot(createTaskSnapshot()) whenever(activityManager.takeTaskThumbnail(taskId)).thenReturn(thumbnailData) 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/monet/ColorSchemeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt index 9f0e67b60c29..85cc88dd5283 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/monet/ColorSchemeTest.kt @@ -15,11 +15,13 @@ */ package com.android.systemui.monet -import androidx.test.filters.SmallTest import android.testing.AndroidTestingRunner import android.util.Log +import android.util.Pair +import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.theme.DynamicColors +import com.google.ux.material.libmonet.dynamiccolor.DynamicColor import com.google.ux.material.libmonet.hct.Hct import com.google.ux.material.libmonet.scheme.SchemeTonalSpot import java.io.File @@ -81,6 +83,10 @@ private fun commentShade(paletteName: String, tone: Int): String { @SmallTest @RunWith(AndroidTestingRunner::class) class ColorSchemeTest : SysuiTestCase() { + private val defaultContrast = 0.0 + private val defaultIsDark = false + private val defaultIsFidelity = false + @Test fun generateThemeStyles() { val document = buildDoc<Any>() @@ -107,7 +113,7 @@ class ColorSchemeTest : SysuiTestCase() { } val style = document.createElement(styleValue.name.lowercase()) - val colorScheme = ColorScheme(sourceColor.toInt(), false, styleValue) + val colorScheme = ColorScheme(sourceColor.toInt(), defaultIsDark, styleValue) style.appendChild( document.createTextNode( @@ -139,7 +145,7 @@ class ColorSchemeTest : SysuiTestCase() { document.appendWithBreak(resources) // shade colors - val colorScheme = ColorScheme(GOOGLE_BLUE, false) + val colorScheme = ColorScheme(GOOGLE_BLUE, defaultIsDark) arrayOf( Triple("accent1", "Primary", colorScheme.accent1), Triple("accent2", "Secondary", colorScheme.accent2), @@ -162,24 +168,35 @@ class ColorSchemeTest : SysuiTestCase() { resources.appendWithBreak(document.createComment(commentRoles), 2) - // dynamic colors - arrayOf(false, true).forEach { isDark -> - val suffix = if (isDark) "_dark" else "_light" - val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, 0.5) - DynamicColors.allDynamicColorsMapped(false).forEach { - resources.createColorEntry( - "system_${it.first}$suffix", - it.second.getArgb(dynamicScheme) - ) + fun generateDynamic(pairs: List<Pair<String, DynamicColor>>) { + arrayOf(false, true).forEach { isDark -> + val suffix = if (isDark) "_dark" else "_light" + val dynamicScheme = + SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), isDark, defaultContrast) + pairs.forEach { + resources.createColorEntry( + "system_${it.first}$suffix", + it.second.getArgb(dynamicScheme) + ) + } } } + // dynamic colors + generateDynamic(DynamicColors.allDynamicColorsMapped(defaultIsFidelity)) + // fixed colors - val dynamicScheme = SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), false, 0.5) - DynamicColors.getFixedColorsMapped(false).forEach { + val dynamicScheme = + SchemeTonalSpot(Hct.fromInt(GOOGLE_BLUE), defaultIsDark, defaultContrast) + DynamicColors.getFixedColorsMapped(defaultIsFidelity).forEach { resources.createColorEntry("system_${it.first}", it.second.getArgb(dynamicScheme)) } + resources.appendWithBreak(document.createComment(commentRoles), 2) + + // custom colors + generateDynamic(DynamicColors.getCustomColorsMapped(defaultIsFidelity)) + saveFile(document, "role_values.xml") } diff --git a/packages/SystemUI/tests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt new file mode 100644 index 000000000000..e81e42b99442 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.motion + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import org.junit.rules.RuleChain +import platform.test.motion.MotionTestRule +import platform.test.motion.compose.ComposeToolkit +import platform.test.motion.testing.createGoldenPathManager +import platform.test.screenshot.DeviceEmulationSpec +import platform.test.screenshot.Displays +import platform.test.screenshot.PathConfig +import platform.test.screenshot.utils.compose.ComposeScreenshotTestRule + +/** Create a [MotionTestRule] for motion tests of Compose-based System UI. */ +fun createSysUiComposeMotionTestRule( + kosmos: Kosmos, + deviceEmulationSpec: DeviceEmulationSpec = DeviceEmulationSpec(Displays.Phone), + pathConfig: PathConfig = PathConfig(), +): MotionTestRule<ComposeToolkit> { + val goldenPathManager = + createGoldenPathManager("frameworks/base/packages/SystemUI/tests/goldens", pathConfig) + val testScope = kosmos.testScope + + val composeScreenshotTestRule = + ComposeScreenshotTestRule(deviceEmulationSpec, goldenPathManager) + + return MotionTestRule( + ComposeToolkit(composeScreenshotTestRule.composeRule, testScope), + goldenPathManager, + bitmapDiffer = composeScreenshotTestRule, + extraRules = RuleChain.outerRule(composeScreenshotTestRule) + ) +} 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/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 890e1e011f22..0998c0c3d32c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -94,6 +94,8 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; @@ -1576,17 +1578,19 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @DisableFlags({ + android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS, + android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL + }) public void testUpdateGeneratedPreview_flagDisabled() { - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); verify(mAppWidgetManager, times(0)).setWidgetPreview(any(), anyInt(), any()); } @Test + @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS) + @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL) public void testUpdateGeneratedPreview_userLocked() { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false); mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); @@ -1594,9 +1598,9 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS) + @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL) public void testUpdateGeneratedPreview_userUnlocked() { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); @@ -1605,9 +1609,9 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS) + @DisableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL) public void testUpdateGeneratedPreview_doesNotSetTwice() { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.disableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); @@ -1617,9 +1621,11 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags({ + android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS, + android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL + }) public void testUpdateGeneratedPreviewWithDataParcel_userLocked() throws InterruptedException { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(false); mManager.updateGeneratedPreviewForUser(mUserTracker.getUserHandle()); @@ -1628,10 +1634,12 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags({ + android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS, + android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL + }) public void testUpdateGeneratedPreviewWithDataParcel_userUnlocked() throws InterruptedException { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); @@ -1641,10 +1649,12 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { } @Test + @EnableFlags({ + android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS, + android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL + }) public void testUpdateGeneratedPreviewWithDataParcel_doesNotSetTwice() throws InterruptedException { - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_GENERATED_PREVIEWS); - mSetFlagsRule.enableFlags(android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL); when(mUserManager.isUserUnlocked(mUserTracker.getUserHandle())).thenReturn(true); when(mAppWidgetManager.setWidgetPreview(any(), anyInt(), any())).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index 9429725718db..b95d3aaef32f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -44,7 +44,6 @@ import android.os.Handler; import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.view.View; import androidx.test.filters.SmallTest; @@ -53,6 +52,7 @@ import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; +import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastSender; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.settings.UserTracker; @@ -88,7 +88,9 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { @Mock private UserTracker mUserTracker; @Mock - private View mView; + private Expandable mExpandable; + @Mock + private DialogTransitionAnimator.Controller mController; @Mock private SystemUIDialog.Factory mSystemUIDialogFactory; @Mock @@ -234,32 +236,31 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { @Test public void testDialogStartedFromLauncher_viewVisible() { - when(mBatteryController.getLastPowerSaverStartView()) - .thenReturn(new WeakReference<>(mView)); - when(mView.isAggregatedVisible()).thenReturn(true); + when(mBatteryController.getLastPowerSaverStartExpandable()) + .thenReturn(new WeakReference<>(mExpandable)); + when(mExpandable.dialogTransitionController(any())).thenReturn(mController); Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION); intent.putExtras(new Bundle()); mReceiver.onReceive(mContext, intent); - verify(mDialogTransitionAnimator).showFromView(any(), eq(mView), any()); + verify(mDialogTransitionAnimator).show(any(), eq(mController)); mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss(); } @Test public void testDialogStartedNotFromLauncher_viewNotVisible() { - when(mBatteryController.getLastPowerSaverStartView()) - .thenReturn(new WeakReference<>(mView)); - when(mView.isAggregatedVisible()).thenReturn(false); + when(mBatteryController.getLastPowerSaverStartExpandable()) + .thenReturn(new WeakReference<>(mExpandable)); Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION); intent.putExtras(new Bundle()); mReceiver.onReceive(mContext, intent); - verify(mDialogTransitionAnimator, never()).showFromView(any(), any()); + verify(mDialogTransitionAnimator, never()).show(any(), any()); verify(mPowerNotificationWarnings.getSaverConfirmationDialog()).show(); mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss(); @@ -267,7 +268,7 @@ public class PowerNotificationWarningsTest extends SysuiTestCase { @Test public void testDialogShownNotFromLauncher() { - when(mBatteryController.getLastPowerSaverStartView()).thenReturn(null); + when(mBatteryController.getLastPowerSaverStartExpandable()).thenReturn(null); Intent intent = new Intent(BatterySaverUtils.ACTION_SHOW_START_SAVER_CONFIRMATION); intent.putExtras(new Bundle()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index ef7798e545d3..5e14b1a60ddb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -43,7 +43,6 @@ import android.os.Looper; import android.os.UserHandle; import android.testing.AndroidTestingRunner; import android.util.SparseArray; -import android.view.View; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -51,6 +50,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.util.CollectionUtils; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.Expandable; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.nano.SystemUIProtoDump; import com.android.systemui.flags.FakeFeatureFlags; @@ -734,7 +734,7 @@ public class QSTileHostTest extends SysuiTestCase { } @Override - protected void handleClick(@Nullable View view) {} + protected void handleClick(@Nullable Expandable expandable) {} @Override protected void handleUpdateState(State state, Object arg) {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt index 629c663943db..bc947fb910e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt @@ -3,6 +3,7 @@ package com.android.systemui.qs import android.content.ComponentName import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner +import android.widget.Switch import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTile @@ -66,15 +67,19 @@ class TileStateToProtoTest : SysuiTestCase() { assertThat(proto?.hasBooleanState()).isFalse() } + /** + * The [QSTile.AdapterState.expandedAccessibilityClassName] setting to [Switch] results in the + * proto having a booleanState. The value of that boolean is true iff the tile is active. + */ @Test - fun booleanState_ACTIVE() { + fun adapterState_ACTIVE() { val state = - QSTile.BooleanState().apply { + QSTile.AdapterState().apply { spec = TEST_SPEC label = TEST_LABEL secondaryLabel = TEST_SUBTITLE state = Tile.STATE_ACTIVE - value = true + expandedAccessibilityClassName = Switch::class.java.name } val proto = state.toProto() @@ -89,6 +94,33 @@ class TileStateToProtoTest : SysuiTestCase() { assertThat(proto?.booleanState).isTrue() } + /** + * Similar to [adapterState_ACTIVE], the use of + * [QSTile.AdapterState.expandedAccessibilityClassName] signals that the tile is toggleable. + */ + @Test + fun adapterState_INACTIVE() { + val state = + QSTile.AdapterState().apply { + spec = TEST_SPEC + label = TEST_LABEL + secondaryLabel = TEST_SUBTITLE + state = Tile.STATE_INACTIVE + expandedAccessibilityClassName = Switch::class.java.name + } + val proto = state.toProto() + + assertThat(proto).isNotNull() + assertThat(proto?.hasSpec()).isTrue() + assertThat(proto?.spec).isEqualTo(TEST_SPEC) + assertThat(proto?.hasComponentName()).isFalse() + assertThat(proto?.label).isEqualTo(TEST_LABEL) + assertThat(proto?.secondaryLabel).isEqualTo(TEST_SUBTITLE) + assertThat(proto?.state).isEqualTo(Tile.STATE_INACTIVE) + assertThat(proto?.hasBooleanState()).isTrue() + assertThat(proto?.booleanState).isFalse() + } + @Test fun noSpec_returnsNull() { val state = diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index df0ab341b5d4..8bf743884359 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -44,13 +44,13 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.TextUtils; import android.util.ArraySet; -import android.view.View; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.Expandable; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.qs.QSHost; import com.android.systemui.res.R; @@ -395,13 +395,13 @@ public class TileQueryHelperTest extends SysuiTestCase { } @Override - public void click(@Nullable View view) {} + public void click(@Nullable Expandable expandable) {} @Override - public void secondaryClick(@Nullable View view) {} + public void secondaryClick(@Nullable Expandable expandable) {} @Override - public void longClick(@Nullable View view) {} + public void longClick(@Nullable Expandable expandable) {} @Override public void userSwitch(int currentUser) {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index ef979d2d0cac..a8e9db5d527c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -35,11 +35,10 @@ import androidx.test.filters.SmallTest import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.IWindowManager -import android.view.View import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator -import com.android.systemui.animation.view.LaunchableFrameLayout +import com.android.systemui.animation.Expandable import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile @@ -339,7 +338,7 @@ class CustomTileTest : SysuiTestCase() { tile.qsTile.activityLaunchForClick = pi } - tile.handleClick(mock(View::class.java)) + tile.handleClick(mock(Expandable::class.java)) testableLooper.processAllMessages() verify(activityStarter, never()) @@ -366,7 +365,7 @@ class CustomTileTest : SysuiTestCase() { val tile = CustomTile.create(customTileFactory, TILE_SPEC, mContext) tile.qsTile.activityLaunchForClick = pi - tile.handleClick(mock(LaunchableFrameLayout::class.java)) + tile.handleClick(mock(Expandable::class.java)) testableLooper.processAllMessages() 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 0a36ae6a4c57..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 @@ -15,10 +15,13 @@ */ 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; @@ -52,14 +55,17 @@ import android.net.Uri; import android.os.Bundle; 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; @@ -71,18 +77,31 @@ 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 = mock(BroadcastDispatcher.class); private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class); private final ActivityManager mActivityManager = mock(ActivityManager.class); + private final IDeviceIdleController mDeviceIdleController = mock(IDeviceIdleController.class); private ComponentName mTileServiceComponentName; private Intent mTileServiceIntent; @@ -95,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); @@ -126,6 +150,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mTileServiceIntent, mUser, mActivityManager, + mDeviceIdleController, mExecutor); } @@ -259,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); @@ -275,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(); @@ -386,6 +448,20 @@ public class TileLifecycleManagerTest extends SysuiTestCase { } @Test + public void testClickCallsDeviceIdleManager() throws Exception { + mStateManager.onTileAdded(); + mStateManager.onStartListening(); + mStateManager.onClick(null); + mStateManager.executeSetBindService(true); + mExecutor.runAllReady(); + + verify(mMockTileService).onClick(null); + verify(mDeviceIdleController).addPowerSaveTempWhitelistApp( + mTileServiceComponentName.getPackageName(), 15000, + mUser.getIdentifier(), REASON_TILE_ONCLICK, "tile onclick"); + } + + @Test public void testFalseBindCallsUnbind() { Context falseContext = mock(Context.class); when(falseContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(false); @@ -396,6 +472,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mTileServiceIntent, mUser, mActivityManager, + mDeviceIdleController, mExecutor); manager.executeSetBindService(true); @@ -418,6 +495,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mTileServiceIntent, mUser, mActivityManager, + mDeviceIdleController, mExecutor); manager.executeSetBindService(true); @@ -440,6 +518,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mTileServiceIntent, mUser, mActivityManager, + mDeviceIdleController, mExecutor); manager.executeSetBindService(true); @@ -464,6 +543,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mTileServiceIntent, mUser, mActivityManager, + mDeviceIdleController, mExecutor); manager.executeSetBindService(true); 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..d15cfbf537a2 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 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) + ) + } + + private val underTest = with(kosmos) { gridConsistencyInteractor } + + @Before + fun setUp() { + with(kosmos) { gridLayoutTypeRepository.setLayout(InfiniteGridLayoutType) } + underTest.start() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun changeLayoutType_usesCorrectGridConsistencyInteractor() = + with(kosmos) { + testScope.runTest { + // Using the no-op grid consistency interactor + gridLayoutTypeRepository.setLayout(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/panels/domain/repository/IconTilesRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt deleted file mode 100644 index 8cc3a85ef6c8..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/repository/IconTilesRepositoryImplTest.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.qs.panels.domain.repository - -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.qs.panels.data.repository.IconTilesRepositoryImpl -import com.android.systemui.qs.pipeline.shared.TileSpec -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runTest -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidJUnit4::class) -class IconTilesRepositoryImplTest : SysuiTestCase() { - - private val underTest = IconTilesRepositoryImpl() - - @Test - fun iconTilesSpecsIsValid() = runTest { - val tilesSpecs by collectLastValue(underTest.iconTilesSpecs) - assertThat(tilesSpecs).isEqualTo(ICON_ONLY_TILES_SPECS) - } - - companion object { - private val ICON_ONLY_TILES_SPECS = - setOf( - TileSpec.create("airplane"), - TileSpec.create("battery"), - TileSpec.create("cameratoggle"), - TileSpec.create("cast"), - TileSpec.create("color_correction"), - TileSpec.create("inversion"), - TileSpec.create("saver"), - TileSpec.create("dnd"), - TileSpec.create("flashlight"), - TileSpec.create("location"), - TileSpec.create("mictoggle"), - TileSpec.create("nfc"), - TileSpec.create("night"), - TileSpec.create("rotation") - ) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt deleted file mode 100644 index e8c5fd978319..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/viewmodel/TileGridViewModelTest.kt +++ /dev/null @@ -1,84 +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.qs.panels.ui.viewmodel - -import android.testing.AndroidTestingRunner -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.qs.QSTile -import com.android.systemui.qs.FakeQSFactory -import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository -import com.android.systemui.qs.pipeline.domain.interactor.FakeQSTile -import com.android.systemui.qs.pipeline.shared.TileSpec -import com.android.systemui.qs.qsTileFactory -import com.android.systemui.testKosmos -import kotlinx.coroutines.test.runTest -import org.junit.Assert -import org.junit.Test -import org.junit.runner.RunWith - -@SmallTest -@RunWith(AndroidTestingRunner::class) -class TileGridViewModelTest : SysuiTestCase() { - - private val kosmos = testKosmos().apply { qsTileFactory = FakeQSFactory(::tileCreator) } - private val underTest = with(kosmos) { tileGridViewModel } - - @Test - fun noIconTiles() = - with(kosmos) { - testScope.runTest { - val latest by collectLastValue(underTest.tileViewModels) - - tileSpecRepository.setTiles( - 0, - listOf( - TileSpec.create("bluetooth"), - TileSpec.create("internet"), - TileSpec.create("alarm") - ) - ) - - latest!!.forEach { Assert.assertFalse(it.iconOnly) } - } - } - - @Test - fun withIconTiles() = - with(kosmos) { - testScope.runTest { - val latest by collectLastValue(underTest.tileViewModels) - - tileSpecRepository.setTiles( - 0, - listOf( - TileSpec.create("airplane"), - TileSpec.create("flashlight"), - TileSpec.create("rotation") - ) - ) - - latest!!.forEach { Assert.assertTrue(it.iconOnly) } - } - } - - private fun tileCreator(spec: String): QSTile { - return FakeQSTile(0).apply { tileSpec = spec } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 22b1c7b58ab3..c70624411c37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -51,7 +51,6 @@ import android.service.quicksettings.Tile; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; -import android.view.View; import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; @@ -63,6 +62,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.systemui.InstanceIdSequenceFake; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.Expandable; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -148,7 +148,7 @@ public class QSTileImplTest extends SysuiTestCase { @Test public void testClick_Metrics() { - mTile.click(null /* view */); + mTile.click(null /* expandable */); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_CLICK))); assertEquals(1, mUiEventLoggerFake.numLogs()); UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0); @@ -159,7 +159,7 @@ public class QSTileImplTest extends SysuiTestCase { public void testClick_log() { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - mTile.click(null /* view */); + mTile.click(null /* expandable */); verify(mQsLogger).logTileClick(eq(SPEC), eq(StatusBarState.SHADE), eq(Tile.STATE_ACTIVE), anyInt()); } @@ -184,7 +184,7 @@ public class QSTileImplTest extends SysuiTestCase { @Test public void testClick_Metrics_Status_Bar_Status() { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - mTile.click(null /* view */); + mTile.click(null /* expandable */); verify(mMetricsLogger).write(mLogCaptor.capture()); assertEquals(StatusBarState.SHADE, mLogCaptor.getValue() .getTaggedData(FIELD_STATUS_BAR_STATE)); @@ -193,12 +193,12 @@ public class QSTileImplTest extends SysuiTestCase { @Test public void testClick_falsing() { mFalsingManager.setFalseTap(true); - mTile.click(null /* view */); + mTile.click(null /* expandable */); mTestableLooper.processAllMessages(); assertThat(mTile.mClicked).isFalse(); mFalsingManager.setFalseTap(false); - mTile.click(null /* view */); + mTile.click(null /* expandable */); mTestableLooper.processAllMessages(); assertThat(mTile.mClicked).isTrue(); } @@ -206,19 +206,19 @@ public class QSTileImplTest extends SysuiTestCase { @Test public void testLongClick_falsing() { mFalsingManager.setFalseLongTap(true); - mTile.longClick(null /* view */); + mTile.longClick(null /* expandable */); mTestableLooper.processAllMessages(); assertThat(mTile.mLongClicked).isFalse(); mFalsingManager.setFalseLongTap(false); - mTile.longClick(null /* view */); + mTile.longClick(null /* expandable */); mTestableLooper.processAllMessages(); assertThat(mTile.mLongClicked).isTrue(); } @Test public void testSecondaryClick_Metrics() { - mTile.secondaryClick(null /* view */); + mTile.secondaryClick(null /* expandable */); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK))); assertEquals(1, mUiEventLoggerFake.numLogs()); UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0); @@ -229,7 +229,7 @@ public class QSTileImplTest extends SysuiTestCase { public void testSecondaryClick_log() { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - mTile.secondaryClick(null /* view */); + mTile.secondaryClick(null /* expandable */); verify(mQsLogger).logTileSecondaryClick(eq(SPEC), eq(StatusBarState.SHADE), eq(Tile.STATE_ACTIVE), anyInt()); } @@ -254,7 +254,7 @@ public class QSTileImplTest extends SysuiTestCase { @Test public void testSecondaryClick_Metrics_Status_Bar_Status() { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); - mTile.secondaryClick(null /* view */); + mTile.secondaryClick(null /* expandable */); verify(mMetricsLogger).write(mLogCaptor.capture()); assertEquals(StatusBarState.KEYGUARD, mLogCaptor.getValue() .getTaggedData(FIELD_STATUS_BAR_STATE)); @@ -262,7 +262,7 @@ public class QSTileImplTest extends SysuiTestCase { @Test public void testLongClick_Metrics() { - mTile.longClick(null /* view */); + mTile.longClick(null /* expandable */); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_LONG_PRESS))); assertEquals(1, mUiEventLoggerFake.numLogs()); UiEventLoggerFake.FakeUiEvent event = mUiEventLoggerFake.get(0); @@ -274,7 +274,7 @@ public class QSTileImplTest extends SysuiTestCase { public void testLongClick_log() { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - mTile.longClick(null /* view */); + mTile.longClick(null /* expandable */); verify(mQsLogger).logTileLongClick(eq(SPEC), eq(StatusBarState.SHADE), eq(Tile.STATE_ACTIVE), anyInt()); } @@ -299,7 +299,7 @@ public class QSTileImplTest extends SysuiTestCase { @Test public void testLongClick_Metrics_Status_Bar_Status() { when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); - mTile.click(null /* view */); + mTile.click(null /* expandable */); verify(mMetricsLogger).write(mLogCaptor.capture()); assertEquals(StatusBarState.SHADE_LOCKED, mLogCaptor.getValue() .getTaggedData(FIELD_STATUS_BAR_STATE)); @@ -560,12 +560,12 @@ public class QSTileImplTest extends SysuiTestCase { } @Override - protected void handleClick(@Nullable View view) { + protected void handleClick(@Nullable Expandable expandable) { mClicked = true; } @Override - protected void handleLongClick(@Nullable View view) { + protected void handleLongClick(@Nullable Expandable expandable) { mLongClicked = true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt index b5ef8c26a7ce..db11c3e89160 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt @@ -500,6 +500,42 @@ class QSTileViewImplTest : SysuiTestCase() { ) } + @Test + fun onActivityLaunchAnimationEnd_onFreshTile_longPressPropertiesAreReset() { + // WHEN an activity launch animation ends on a fresh tile + tileView.onActivityLaunchAnimationEnd() + + // THEN the tile's long-press effect properties are reset by default + assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue() + } + + @Test + fun onUpdateLongPressEffectProperties_duringLongPressEffect_propertiesAreNotReset() { + // GIVEN a state that supports long-press + val state = QSTile.State() + tileView.changeState(state) + + // WHEN the long-press effect is updating the properties + tileView.updateLongPressEffectProperties(1f) + + // THEN the tile's long-press effect properties haven't reset + assertThat(tileView.haveLongPressPropertiesBeenReset).isFalse() + } + + @Test + fun onActivityLaunchAnimationEnd_afterLongPressEffect_longPressPropertiesAreReset() { + // GIVEN a state that supports long-press and the long-press effect updating + val state = QSTile.State() + tileView.changeState(state) + tileView.updateLongPressEffectProperties(1f) + + // WHEN an activity launch animation ends on a fresh tile + tileView.onActivityLaunchAnimationEnd() + + // THEN the tile's long-press effect properties are reset + assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue() + } + class FakeTileView( context: Context, collapsed: Boolean, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt index e2a3fac60ee8..ad87315c34f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt @@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.systemui.res.R +import com.android.internal.telephony.flags.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.classifier.FalsingManagerFake @@ -33,10 +33,12 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.util.settings.GlobalSettings import com.google.common.truth.Truth.assertThat import dagger.Lazy +import kotlinx.coroutines.Job import org.junit.After import org.junit.Before import org.junit.Test @@ -44,11 +46,15 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.times +import org.mockito.kotlin.verify @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class AirplaneModeTileTest : SysuiTestCase() { + @Mock private lateinit var mHost: QSHost @Mock @@ -62,7 +68,9 @@ class AirplaneModeTileTest : SysuiTestCase() { @Mock private lateinit var mBroadcastDispatcher: BroadcastDispatcher @Mock - private lateinit var mConnectivityManager: Lazy<ConnectivityManager> + private lateinit var mLazyConnectivityManager: Lazy<ConnectivityManager> + @Mock + private lateinit var mConnectivityManager: ConnectivityManager @Mock private lateinit var mGlobalSettings: GlobalSettings @Mock @@ -72,13 +80,15 @@ class AirplaneModeTileTest : SysuiTestCase() { private lateinit var mTestableLooper: TestableLooper private lateinit var mTile: AirplaneModeTile + @Mock + private lateinit var mClickJob: Job @Before fun setUp() { MockitoAnnotations.initMocks(this) mTestableLooper = TestableLooper.get(this) Mockito.`when`(mHost.context).thenReturn(mContext) Mockito.`when`(mHost.userContext).thenReturn(mContext) - + Mockito.`when`(mLazyConnectivityManager.get()).thenReturn(mConnectivityManager) mTile = AirplaneModeTile( mHost, mUiEventLogger, @@ -90,7 +100,7 @@ class AirplaneModeTileTest : SysuiTestCase() { mActivityStarter, mQsLogger, mBroadcastDispatcher, - mConnectivityManager, + mLazyConnectivityManager, mGlobalSettings, mUserTracker) } @@ -120,4 +130,24 @@ class AirplaneModeTileTest : SysuiTestCase() { assertThat(state.icon) .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on)) } + + @Test + fun handleClick_noSatelliteFeature_directSetAirplaneMode() { + mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + + mTile.handleClick(null) + + verify(mConnectivityManager).setAirplaneMode(any()) + } + + @Test + fun handleClick_hasSatelliteFeatureButClickIsProcessing_doNothing() { + mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + Mockito.`when`(mClickJob.isCompleted).thenReturn(false) + mTile.mClickJob = mClickJob + + mTile.handleClick(null) + + verify(mConnectivityManager, times(0)).setAirplaneMode(any()) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt index 605dc1402fc3..2c49e925edd6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt @@ -21,11 +21,10 @@ import android.os.Handler import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper -import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.Expandable import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile @@ -34,6 +33,7 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings @@ -59,24 +59,15 @@ class BatterySaverTileTest : SysuiTestCase() { private const val USER = 10 } - @Mock - private lateinit var userContext: Context - @Mock - private lateinit var qsHost: QSHost - @Mock - private lateinit var uiEventLogger: QsEventLogger - @Mock - private lateinit var metricsLogger: MetricsLogger - @Mock - private lateinit var statusBarStateController: StatusBarStateController - @Mock - private lateinit var activityStarter: ActivityStarter - @Mock - private lateinit var qsLogger: QSLogger - @Mock - private lateinit var batteryController: BatteryController - @Mock - private lateinit var view: View + @Mock private lateinit var userContext: Context + @Mock private lateinit var qsHost: QSHost + @Mock private lateinit var uiEventLogger: QsEventLogger + @Mock private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var qsLogger: QSLogger + @Mock private lateinit var batteryController: BatteryController + @Mock private lateinit var expandable: Expandable private lateinit var secureSettings: SecureSettings private lateinit var testableLooper: TestableLooper private lateinit var tile: BatterySaverTile @@ -91,7 +82,8 @@ class BatterySaverTileTest : SysuiTestCase() { secureSettings = FakeSettings() - tile = BatterySaverTile( + tile = + BatterySaverTile( qsHost, uiEventLogger, testableLooper.looper, @@ -102,7 +94,8 @@ class BatterySaverTileTest : SysuiTestCase() { activityStarter, qsLogger, batteryController, - secureSettings) + secureSettings + ) tile.initialize() testableLooper.processAllMessages() @@ -131,23 +124,23 @@ class BatterySaverTileTest : SysuiTestCase() { @Test fun testClickingPowerSavePassesView() { tile.onPowerSaveChanged(true) - tile.handleClick(view) + tile.handleClick(expandable) tile.onPowerSaveChanged(false) - tile.handleClick(view) + tile.handleClick(expandable) - verify(batteryController).setPowerSaveMode(true, view) - verify(batteryController).setPowerSaveMode(false, view) + verify(batteryController).setPowerSaveMode(true, expandable) + verify(batteryController).setPowerSaveMode(false, expandable) } @Test fun testStopListeningClearsViewInController() { clearInvocations(batteryController) tile.handleSetListening(true) - verify(batteryController, never()).clearLastPowerSaverStartView() + verify(batteryController, never()).clearLastPowerSaverStartExpandable() tile.handleSetListening(false) - verify(batteryController).clearLastPowerSaverStartView() + verify(batteryController).clearLastPowerSaverStartExpandable() } @Test @@ -158,7 +151,7 @@ class BatterySaverTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_off)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_off)) } @Test @@ -169,6 +162,6 @@ class BatterySaverTileTest : SysuiTestCase() { tile.handleUpdateState(state, /* arg= */ null) assertThat(state.icon) - .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_on)) + .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_on)) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 830f08a0c445..1ffbb7be49fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -9,10 +9,11 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger +import com.android.internal.telephony.flags.Flags import com.android.settingslib.Utils import com.android.settingslib.bluetooth.CachedBluetoothDevice -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.flags.FeatureFlagsClassic import com.android.systemui.plugins.ActivityStarter @@ -23,13 +24,14 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl -import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogViewModel +import com.android.systemui.res.R import com.android.systemui.statusbar.policy.BluetoothController 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.google.common.truth.Truth.assertThat +import kotlinx.coroutines.Job import org.junit.After import org.junit.Before import org.junit.Test @@ -37,6 +39,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -54,7 +57,7 @@ class BluetoothTileTest : SysuiTestCase() { @Mock private lateinit var uiEventLogger: QsEventLogger @Mock private lateinit var featureFlags: FeatureFlagsClassic @Mock private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel - + @Mock private lateinit var clickJob: Job private lateinit var testableLooper: TestableLooper private lateinit var tile: FakeBluetoothTile @@ -191,6 +194,41 @@ class BluetoothTileTest : SysuiTestCase() { } @Test + fun handleClick_hasSatelliteFeatureButNoQsTileDialogAndClickIsProcessing_doNothing() { + mSetFlagsRule.enableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) + .thenReturn(false) + `when`(clickJob.isCompleted).thenReturn(false) + tile.mClickJob = clickJob + + tile.handleClick(null) + + verify(bluetoothController, times(0)).setBluetoothEnabled(any()) + } + + @Test + fun handleClick_noSatelliteFeatureAndNoQsTileDialog_directSetBtEnable() { + mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) + .thenReturn(false) + + tile.handleClick(null) + + verify(bluetoothController).setBluetoothEnabled(any()) + } + + @Test + fun handleClick_noSatelliteFeatureButHasQsTileDialog_showDialog() { + mSetFlagsRule.disableFlags(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + `when`(featureFlags.isEnabled(com.android.systemui.flags.Flags.BLUETOOTH_QS_TILE_DIALOG)) + .thenReturn(true) + + tile.handleClick(null) + + verify(bluetoothTileDialogViewModel).showDialog(null) + } + + @Test fun testMetadataListener_whenDisconnected_isUnregistered() { val state = QSTile.BooleanState() val cachedDevice = mock<CachedBluetoothDevice>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index cca1344424ac..1173fa31fbb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -26,12 +26,12 @@ import android.provider.Settings.Global.ZEN_MODE_OFF import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.ContextThemeWrapper -import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile @@ -42,7 +42,6 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.ZenModeController import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings @@ -99,6 +98,12 @@ class DndTileTest : SysuiTestCase() { @Mock private lateinit var hostDialog: Dialog + @Mock + private lateinit var expandable: Expandable + + @Mock + private lateinit var controller: DialogTransitionAnimator.Controller + private lateinit var secureSettings: SecureSettings private lateinit var testableLooper: TestableLooper private lateinit var tile: DndTile @@ -119,6 +124,7 @@ class DndTileTest : SysuiTestCase() { } } whenever(qsHost.context).thenReturn(wrappedContext) + whenever(expandable.dialogTransitionController(any())).thenReturn(controller) tile = DndTile( qsHost, @@ -187,11 +193,10 @@ class DndTileTest : SysuiTestCase() { secureSettings.putIntForUser(KEY, Settings.Secure.ZEN_DURATION_PROMPT, DEFAULT_USER) testableLooper.processAllMessages() - val view = View(context) - tile.handleClick(view) + tile.handleClick(expandable) testableLooper.processAllMessages() - verify(mDialogTransitionAnimator).showFromView(any(), eq(view), nullable(), anyBoolean()) + verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean()) } @Test @@ -201,8 +206,7 @@ class DndTileTest : SysuiTestCase() { secureSettings.putIntForUser(KEY, 60, DEFAULT_USER) testableLooper.processAllMessages() - val view = View(context) - tile.handleClick(view) + tile.handleClick(expandable) testableLooper.processAllMessages() verify(mDialogTransitionAnimator, never()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt index 1f5ebfec1a56..1c42dd15ce3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt @@ -20,12 +20,12 @@ import android.os.Handler import android.provider.Settings import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase import com.android.systemui.accessibility.fontscaling.FontScalingDialogDelegate import com.android.systemui.animation.DialogTransitionAnimator +import com.android.systemui.animation.Expandable import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -37,7 +37,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq -import com.android.systemui.util.mockito.nullable import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After @@ -67,6 +66,8 @@ class FontScalingTileTest : SysuiTestCase() { @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate @Mock private lateinit var dialog: SystemUIDialog + @Mock private lateinit var expandable: Expandable + @Mock private lateinit var controller: DialogTransitionAnimator.Controller private lateinit var testableLooper: TestableLooper private lateinit var systemClock: FakeSystemClock @@ -81,6 +82,7 @@ class FontScalingTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) `when`(qsHost.getContext()).thenReturn(mContext) `when`(fontScalingDialogDelegate.createDialog()).thenReturn(dialog) + `when`(expandable.dialogTransitionController(any())).thenReturn(controller) systemClock = FakeSystemClock() backgroundDelayableExecutor = FakeExecutor(systemClock) @@ -119,8 +121,7 @@ class FontScalingTileTest : SysuiTestCase() { @Test fun clickTile_screenUnlocked_showDialogAnimationFromView() { `when`(keyguardStateController.isShowing).thenReturn(false) - val view = View(context) - fontScalingTile.click(view) + fontScalingTile.click(expandable) testableLooper.processAllMessages() verify(activityStarter) @@ -132,14 +133,13 @@ class FontScalingTileTest : SysuiTestCase() { eq(false) ) argumentCaptor.value.run() - verify(mDialogTransitionAnimator).showFromView(any(), eq(view), nullable(), anyBoolean()) + verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean()) } @Test fun clickTile_onLockScreen_neverShowDialogAnimationFromView() { `when`(keyguardStateController.isShowing).thenReturn(true) - val view = View(context) - fontScalingTile.click(view) + fontScalingTile.click(expandable) testableLooper.processAllMessages() verify(activityStarter) @@ -151,8 +151,7 @@ class FontScalingTileTest : SysuiTestCase() { eq(false) ) argumentCaptor.value.run() - verify(mDialogTransitionAnimator, never()) - .showFromView(any(), eq(view), nullable(), anyBoolean()) + verify(mDialogTransitionAnimator, never()).show(any(), any(), anyBoolean()) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java index 73aa54cef66a..56671bf357ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java @@ -38,6 +38,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager; +import com.android.systemui.animation.Expandable; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -135,10 +136,10 @@ public class HearingDevicesTileTest extends SysuiTestCase { @Test public void handleClick_dialogShown() { - View view = new View(mContext); - mTile.handleClick(view); + Expandable expandable = Expandable.fromView(new View(mContext)); + mTile.handleClick(expandable); mTestableLooper.processAllMessages(); - verify(mHearingDevicesDialogManager).showDialog(view); + verify(mHearingDevicesDialogManager).showDialog(expandable); } } 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 387f27d048ea..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 } ) } @@ -241,6 +241,7 @@ class OverviewProxyServiceTest : SysuiTestCase() { statusBarWinController, sysUiState, mock(), + mock(), userTracker, wakefulnessLifecycle, uiEventLogger, 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/ActionExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt index 91f39126c67c..a10d81f86d8e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionExecutorTest.kt @@ -21,14 +21,12 @@ import android.content.Intent import android.os.Bundle import android.os.UserHandle import android.testing.AndroidTestingRunner -import android.view.View import android.view.Window import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat -import kotlin.test.Ignore import kotlin.test.Test import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestCoroutineScheduler @@ -50,13 +48,12 @@ class ActionExecutorTest : SysuiTestCase() { private val intentExecutor = mock<ActionIntentExecutor>() private val window = mock<Window>() - private val view = mock<View>() + private val viewProxy = mock<ScreenshotShelfViewProxy>() private val onDismiss = mock<(() -> Unit)>() private val pendingIntent = mock<PendingIntent>() private lateinit var actionExecutor: ActionExecutor - @Ignore // Fixed with newer mockito version (in main) @Test fun startSharedTransition_callsLaunchIntent() = runTest { actionExecutor = createActionExecutor() @@ -72,16 +69,16 @@ class ActionExecutorTest : SysuiTestCase() { } @Test - fun sendPendingIntent_dismisses() = runTest { + fun sendPendingIntent_requestsDismissal() = runTest { actionExecutor = createActionExecutor() actionExecutor.sendPendingIntent(pendingIntent) verify(pendingIntent).send(any(Bundle::class.java)) - verify(onDismiss).invoke() + verify(viewProxy).requestDismissal(null) } private fun createActionExecutor(): ActionExecutor { - return ActionExecutor(intentExecutor, testScope, window, view, onDismiss) + return ActionExecutor(intentExecutor, testScope, window, viewProxy, onDismiss) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt index 5e53fe16534d..5cd3f66d2cbf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentExecutorTest.kt @@ -23,17 +23,18 @@ import android.testing.AndroidTestingRunner import android.testing.TestableContext import com.android.systemui.Flags import com.android.systemui.SysuiTestCase +import com.android.systemui.screenshot.proxy.SystemUiProxy import com.android.systemui.settings.DisplayTracker import com.android.systemui.shared.system.ActivityManagerWrapper import com.android.systemui.statusbar.phone.CentralSurfaces -import com.android.systemui.util.mockito.mock import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mockito.verify +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify @RunWith(AndroidTestingRunner::class) class ActionIntentExecutorTest : SysuiTestCase() { @@ -44,8 +45,9 @@ class ActionIntentExecutorTest : SysuiTestCase() { private val testableContext = TestableContext(mContext) private val activityManagerWrapper = mock<ActivityManagerWrapper>() + private val systemUiProxy = mock<SystemUiProxy>() + private val displayTracker = mock<DisplayTracker>() - private val keyguardController = mock<ScreenshotKeyguardController>() private val actionIntentExecutor = ActionIntentExecutor( @@ -53,12 +55,12 @@ class ActionIntentExecutorTest : SysuiTestCase() { activityManagerWrapper, testScope, mainDispatcher, + systemUiProxy, displayTracker, - keyguardController, ) @Test - @EnableFlags(Flags.FLAG_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS) + @EnableFlags(Flags.FLAG_FIX_SCREENSHOT_ACTION_DISMISS_SYSTEM_WINDOWS) fun launchIntent_callsCloseSystemWindows() = testScope.runTest { val intent = Intent(Intent.ACTION_EDIT).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } 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/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index 0f3714385725..bf7d909380df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -69,8 +69,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Before fun setUp() { - whenever(controllerFactory.create(eq(0), any())).thenReturn(controller0) - whenever(controllerFactory.create(eq(1), any())).thenReturn(controller1) + whenever(controllerFactory.create(any(), any())).thenAnswer { + if (it.getArgument<Display>(0).displayId == 0) controller0 else controller1 + } whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0) whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1) } @@ -78,12 +79,14 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Test fun executeScreenshots_severalDisplays_callsControllerForEachOne() = testScope.runTest { - setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) + val internalDisplay = display(TYPE_INTERNAL, id = 0) + val externalDisplay = display(TYPE_EXTERNAL, id = 1) + setDisplays(internalDisplay, externalDisplay) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) - verify(controllerFactory).create(eq(0), any()) - verify(controllerFactory).create(eq(1), any()) + verify(controllerFactory).create(eq(internalDisplay), any()) + verify(controllerFactory).create(eq(externalDisplay), any()) val capturer = ArgumentCaptor<ScreenshotData>() @@ -107,7 +110,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { @Test fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() = testScope.runTest { - setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) + val internalDisplay = display(TYPE_INTERNAL, id = 0) + val externalDisplay = display(TYPE_EXTERNAL, id = 1) + setDisplays(internalDisplay, externalDisplay) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots( createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE), @@ -115,8 +120,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { callback ) - verify(controllerFactory).create(eq(0), any()) - verify(controllerFactory, never()).create(eq(1), any()) + verify(controllerFactory).create(eq(internalDisplay), any()) + verify(controllerFactory, never()).create(eq(externalDisplay), any()) val capturer = ArgumentCaptor<ScreenshotData>() diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt index d44e26c266fc..e32086b79918 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModelTest.kt @@ -34,20 +34,21 @@ class ScreenshotViewModelTest { assertThat(viewModel.actions.value).isEmpty() - viewModel.addAction(appearance, onclick) + viewModel.addAction(appearance, true, onclick) assertThat(viewModel.actions.value).hasSize(1) val added = viewModel.actions.value[0] assertThat(added.appearance).isEqualTo(appearance) assertThat(added.onClicked).isEqualTo(onclick) + assertThat(added.showDuringEntrance).isTrue() } @Test fun testRemoveAction() { val viewModel = ScreenshotViewModel(accessibilityManager) - val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), {}) - val secondId = viewModel.addAction(appearance, onclick) + val firstId = viewModel.addAction(ActionButtonAppearance(null, "", ""), false, {}) + val secondId = viewModel.addAction(appearance, false, onclick) assertThat(viewModel.actions.value).hasSize(2) assertThat(firstId).isNotEqualTo(secondId) @@ -58,13 +59,14 @@ class ScreenshotViewModelTest { val remaining = viewModel.actions.value[0] assertThat(remaining.appearance).isEqualTo(appearance) + assertThat(remaining.showDuringEntrance).isFalse() assertThat(remaining.onClicked).isEqualTo(onclick) } @Test fun testUpdateActionAppearance() { val viewModel = ScreenshotViewModel(accessibilityManager) - val id = viewModel.addAction(appearance, onclick) + val id = viewModel.addAction(appearance, false, onclick) val otherAppearance = ActionButtonAppearance(null, "Other", "Other") viewModel.updateActionAppearance(id, otherAppearance) 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 99204e75e5a3..49a467e152ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.shade import android.graphics.Rect import android.os.PowerManager -import android.platform.test.flag.junit.FlagsParameterization +import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils import android.view.MotionEvent @@ -27,6 +27,7 @@ import android.widget.FrameLayout import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.Flags import com.android.systemui.SysuiTestCase @@ -39,26 +40,22 @@ 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 -import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyguard.domain.interactor.keyguardInteractor -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.res.R -import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlag -import com.android.systemui.scene.shared.model.Scenes import 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 import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -71,14 +68,12 @@ import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -import platform.test.runner.parameterized.ParameterizedAndroidJunit4 -import platform.test.runner.parameterized.Parameters @ExperimentalCoroutinesApi -@RunWith(ParameterizedAndroidJunit4::class) +@RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest -class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : SysuiTestCase() { +class GlanceableHubContainerControllerTest : SysuiTestCase() { private val kosmos: Kosmos = testKosmos().apply { // UnconfinedTestDispatcher makes testing simpler due to CommunalInteractor flows using @@ -88,9 +83,9 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu @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 @@ -100,10 +95,6 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu private lateinit var communalRepository: FakeCommunalRepository private lateinit var underTest: GlanceableHubContainerController - init { - mSetFlagsRule.setFlagsParameterization(flags!!) - } - @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -126,13 +117,12 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, - keyguardTransitionInteractor, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) } @@ -169,13 +159,12 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, - keyguardTransitionInteractor, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) @@ -216,13 +205,39 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu } @Test + fun onTouchEvent_communalTransitioning_interceptsTouches() = + with(kosmos) { + testScope.runTest { + // Communal is opening. + communalRepository.setTransitionState( + flowOf( + ObservableTransitionState.Transition( + fromScene = CommunalScenes.Blank, + toScene = CommunalScenes.Communal, + currentScene = flowOf(CommunalScenes.Blank), + progress = flowOf(0.5f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true) + ) + ) + ) + testableLooper.processAllMessages() + + // Touch events are intercepted. + assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() + // User activity sent to PowerManager. + verify(powerManager).userActivity(any(), any(), any()) + } + } + + @Test fun onTouchEvent_communalOpen_interceptsTouches() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) - // Touch events are intercepted outside of any gesture areas. + // Touch events are intercepted. assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue() // User activity sent to PowerManager. verify(powerManager).userActivity(any(), any(), any()) @@ -288,13 +303,12 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, - keyguardTransitionInteractor, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) @@ -308,13 +322,12 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu GlanceableHubContainerController( communalInteractor, communalViewModel, - dialogFactory, - keyguardTransitionInteractor, keyguardInteractor, shadeInteractor, powerManager, communalColors, ambientTouchComponentFactory, + communalContent, kosmos.sceneDataSourceDelegator, ) @@ -501,13 +514,6 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu } private fun goToScene(scene: SceneKey) { - if (SceneContainerFlag.isEnabled) { - if (scene == CommunalScenes.Communal) { - kosmos.sceneInteractor.changeScene(Scenes.Communal, "test") - } else { - kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "test") - } - } communalRepository.changeScene(scene) testableLooper.processAllMessages() } @@ -536,11 +542,5 @@ class GlanceableHubContainerControllerTest(flags: FlagsParameterization?) : Sysu MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, CONTAINER_WIDTH.toFloat(), 0f, 0) private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) private val UP_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - - @JvmStatic - @Parameters(name = "{0}") - fun getParams(): List<FlagsParameterization> { - return FlagsParameterization.allCombinationsOf().andSceneContainer() - } } } 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..043dba13f616 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -24,6 +24,7 @@ import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer; import static com.google.common.truth.Truth.assertThat; import static kotlinx.coroutines.flow.FlowKt.emptyFlow; +import static kotlinx.coroutines.flow.SharedFlowKt.MutableSharedFlow; import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow; import static org.mockito.ArgumentMatchers.any; @@ -40,6 +41,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.animation.Animator; import android.annotation.IdRes; import android.content.ContentResolver; import android.content.res.Configuration; @@ -203,16 +205,20 @@ import com.android.wm.shell.animation.FlingAnimationUtils; import dagger.Lazy; import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.channels.BufferOverflow; import kotlinx.coroutines.test.TestScope; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.mockito.stubbing.Answer; +import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -347,7 +353,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm; @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver; @Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper; - protected final int mMaxUdfpsBurnInOffsetY = 5; protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic(); protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; @@ -362,8 +367,11 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected PowerInteractor mPowerInteractor; protected FakeHeadsUpNotificationRepository mFakeHeadsUpNotificationRepository = new FakeHeadsUpNotificationRepository(); - protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor = - new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository); + protected NotificationsKeyguardViewStateRepository mNotificationsKeyguardViewStateRepository = + new NotificationsKeyguardViewStateRepository(); + protected NotificationsKeyguardInteractor mNotificationsKeyguardInteractor = + new NotificationsKeyguardInteractor(mNotificationsKeyguardViewStateRepository); + protected HeadsUpNotificationInteractor mHeadsUpNotificationInteractor; protected NotificationPanelViewController.TouchHandler mTouchHandler; protected ConfigurationController mConfigurationController; protected SysuiStatusBarStateController mStatusBarStateController; @@ -387,9 +395,11 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected FragmentHostManager.FragmentListener mFragmentListener; + @Rule(order = 200) + public MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Before public void setup() { - MockitoAnnotations.initMocks(this); mFeatureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false); mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); @@ -411,6 +421,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mPowerInteractor = keyguardInteractorDeps.getPowerInteractor(); when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn( MutableStateFlow(false)); + when(mKeyguardTransitionInteractor.getCurrentKeyguardState()).thenReturn( + MutableSharedFlow(0, 0, BufferOverflow.SUSPEND)); + when(mDeviceEntryFaceAuthInteractor.isBypassEnabled()).thenReturn(MutableStateFlow(false)); DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor = mock(DeviceEntryUdfpsInteractor.class); when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(MutableStateFlow(false)); @@ -446,6 +459,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mUiEventLogger, () -> mKosmos.getInteractionJankMonitor(), mJavaAdapter, + () -> mKeyguardTransitionInteractor, () -> mShadeInteractor, () -> mKosmos.getDeviceUnlockedInteractor(), () -> mKosmos.getSceneInteractor(), @@ -546,6 +560,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { }).when(mView).setOnTouchListener(any(NotificationPanelViewController.TouchHandler.class)); // Dreaming->Lockscreen + when(mKeyguardTransitionInteractor.transition(any())) + .thenReturn(emptyFlow()); when(mKeyguardTransitionInteractor.transition(any(), any())) .thenReturn(emptyFlow()); when(mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha()) @@ -600,6 +616,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { new UiEventLoggerFake(), () -> mKosmos.getInteractionJankMonitor(), mJavaAdapter, + () -> mKeyguardTransitionInteractor, () -> mShadeInteractor, () -> mKosmos.getDeviceUnlockedInteractor(), () -> mKosmos.getSceneInteractor(), @@ -653,13 +670,18 @@ 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()); when(mView.requireViewById(R.id.keyguard_long_press)) .thenReturn(mock(LongPressHandlingView.class)); + mHeadsUpNotificationInteractor = + new HeadsUpNotificationInteractor(mFakeHeadsUpNotificationRepository, + mDeviceEntryFaceAuthInteractor, mKeyguardTransitionInteractor, + mNotificationsKeyguardInteractor, mShadeInteractor); + mNotificationPanelViewController = new NotificationPanelViewController( mView, mMainHandler, @@ -757,6 +779,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Override public void onOpenStarted() {} }); + // Create a set to which the class will add all animators used, so that we can + // verify that they are all stopped. + mNotificationPanelViewController.mTestSetOfAnimatorsUsed = new HashSet<>(); ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); verify(mView, atLeast(1)).addOnAttachStateChangeListener( @@ -818,13 +843,20 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @After public void tearDown() { + List<Animator> leakedAnimators = null; if (mNotificationPanelViewController != null) { mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); mNotificationPanelViewController.cancelHeightAnimator(); + leakedAnimators = mNotificationPanelViewController.mTestSetOfAnimatorsUsed.stream() + .filter(Animator::isRunning).toList(); + mNotificationPanelViewController.mTestSetOfAnimatorsUsed.forEach(Animator::cancel); } if (mMainHandler != null) { mMainHandler.removeCallbacksAndMessages(null); } + if (leakedAnimators != null) { + assertThat(leakedAnimators).isEmpty(); + } } protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding, diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 81e20c17a8ea..65364053f109 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -18,6 +18,7 @@ package com.android.systemui.shade; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; +import static com.android.systemui.Flags.FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING; @@ -47,6 +48,7 @@ import android.animation.ValueAnimator; import android.graphics.Point; import android.os.PowerManager; import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; @@ -426,6 +428,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo public void testOnTouchEvent_expansionResumesAfterBriefTouch() { mFalsingManager.setIsClassifierEnabled(true); mFalsingManager.setIsFalseTouch(false); + mNotificationPanelViewController.setForceFlingAnimationForTest(true); // Start shade collapse with swipe up onTouchEvent(MotionEvent.obtain(0L /* downTime */, 0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */, @@ -454,6 +457,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo // fling should still be called after a touch that does not exceed touch slop assertThat(mNotificationPanelViewController.isClosing()).isTrue(); assertThat(mNotificationPanelViewController.isFlinging()).isTrue(); + mNotificationPanelViewController.setForceFlingAnimationForTest(false); } @Test @@ -675,6 +679,33 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + @EnableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX) + public void testCanBeCollapsed_expandedInKeyguard() { + mStatusBarStateController.setState(KEYGUARD); + mNotificationPanelViewController.setExpandedFraction(1f); + + assertThat(mNotificationPanelViewController.canBeCollapsed()).isFalse(); + } + + @Test + @EnableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX) + public void testCanBeCollapsed_expandedInShade() { + mStatusBarStateController.setState(SHADE); + mNotificationPanelViewController.setExpandedFraction(1f); + assertThat(mNotificationPanelViewController.canBeCollapsed()).isTrue(); + } + + @Test + @DisableFlags(FLAG_SHADE_COLLAPSE_ACTIVITY_LAUNCH_FIX) + public void testCanBeCollapsed_expandedInKeyguard_flagDisabled() { + mStatusBarStateController.setState(KEYGUARD); + mNotificationPanelViewController.setExpandedFraction(1f); + + assertThat(mNotificationPanelViewController.canBeCollapsed()).isTrue(); + } + + @Test + @Ignore("b/341163515 - fails to clean up animators correctly") public void testSwipeWhileLocked_notifiesKeyguardState() { mStatusBarStateController.setState(KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt index 6631d29da719..e1ee3585abe3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerWithCoroutinesTest.kt @@ -251,7 +251,7 @@ class NotificationPanelViewControllerWithCoroutinesTest : // WHEN a pinned heads up is present mFakeHeadsUpNotificationRepository.setNotifications( - fakeHeadsUpRowRepository("key", isPinned = true) + FakeHeadsUpRowRepository("key", isPinned = true) ) } advanceUntilIdle() @@ -274,9 +274,4 @@ class NotificationPanelViewControllerWithCoroutinesTest : // THEN the panel should be visible assertThat(mNotificationPanelViewController.isExpanded).isTrue() } - - private fun fakeHeadsUpRowRepository(key: String, isPinned: Boolean = false) = - FakeHeadsUpRowRepository(key = key, elementKey = Any()).apply { - this.isPinned.value = isPinned - } } 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 112829af2068..4a867a8ecf22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -17,12 +17,15 @@ package com.android.systemui.shade import android.content.Context +import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.FlagsParameterization +import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.view.KeyEvent import android.view.MotionEvent import android.view.View import android.view.ViewGroup +import android.view.ViewTreeObserver import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityContainerController import com.android.keyguard.LegacyLockIconViewController @@ -43,6 +46,7 @@ import com.android.systemui.flags.andSceneContainer import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionStep @@ -72,7 +76,6 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat -import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow @@ -80,12 +83,12 @@ import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Before -import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock import org.mockito.Mockito.anyFloat +import org.mockito.Mockito.atLeast import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -93,13 +96,14 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters +import java.util.Optional import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @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 @@ -152,11 +156,12 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : private lateinit var underTest: NotificationShadeWindowViewController private lateinit var testScope: TestScope + private lateinit var testableLooper: TestableLooper private lateinit var featureFlagsClassic: FakeFeatureFlagsClassic init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -169,7 +174,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : .thenReturn(keyguardBouncerComponent) whenever(keyguardBouncerComponent.securityContainerController) .thenReturn(keyguardSecurityContainerController) - whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING)) + whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING))) .thenReturn(emptyFlow<TransitionStep>()) featureFlagsClassic = FakeFeatureFlagsClassic() @@ -181,6 +186,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) testScope = TestScope() + testableLooper = TestableLooper.get(this) falsingCollector = FalsingCollectorFake() fakeClock = FakeSystemClock() underTest = @@ -407,6 +413,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : } @Test + @DisableSceneContainer fun handleDispatchTouchEvent_glanceableHubIntercepts_returnsTrue() { whenever(mGlanceableHubContainerController.onTouchEvent(DOWN_EVENT)).thenReturn(true) underTest.setStatusBarViewController(phoneStatusBarViewController) @@ -512,46 +519,6 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : } @Test - fun handleExternalTouch_intercepted_sendsOnTouch() { - // Accept dispatch and also intercept. - whenever(view.dispatchTouchEvent(any())).thenReturn(true) - whenever(view.onInterceptTouchEvent(any())).thenReturn(true) - - underTest.handleExternalTouch(DOWN_EVENT) - underTest.handleExternalTouch(MOVE_EVENT) - - // Once intercepted, both events are sent to the view. - verify(view).onTouchEvent(DOWN_EVENT) - verify(view).onTouchEvent(MOVE_EVENT) - } - - @Test - fun handleExternalTouch_notDispatched_interceptNotCalled() { - // Don't accept dispatch - whenever(view.dispatchTouchEvent(any())).thenReturn(false) - - underTest.handleExternalTouch(DOWN_EVENT) - - // Interception is not offered. - verify(view, never()).onInterceptTouchEvent(any()) - } - - @Test - fun handleExternalTouch_notIntercepted_onTouchNotSent() { - // Accept dispatch, but don't dispatch - whenever(view.dispatchTouchEvent(any())).thenReturn(true) - whenever(view.onInterceptTouchEvent(any())).thenReturn(false) - - underTest.handleExternalTouch(DOWN_EVENT) - underTest.handleExternalTouch(MOVE_EVENT) - - // Interception offered for both events, but onTouchEvent is never called. - verify(view).onInterceptTouchEvent(DOWN_EVENT) - verify(view).onInterceptTouchEvent(MOVE_EVENT) - verify(view, never()).onTouchEvent(any()) - } - - @Test fun testGetKeyguardMessageArea() = testScope.runTest { underTest.keyguardMessageArea @@ -559,29 +526,42 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization?) : } @Test - @Ignore("b/321332798") + @DisableSceneContainer fun setsUpCommunalHubLayout_whenFlagEnabled() { whenever(mGlanceableHubContainerController.communalAvailable()) - .thenReturn(MutableStateFlow(true)) + .thenReturn(MutableStateFlow(true)) - val mockCommunalView = mock(View::class.java) + val communalView = View(context) whenever(mGlanceableHubContainerController.initView(any<Context>())) - .thenReturn(mockCommunalView) + .thenReturn(communalView) val mockCommunalPlaceholder = mock(View::class.java) val fakeViewIndex = 20 whenever(view.findViewById<View>(R.id.communal_ui_stub)).thenReturn(mockCommunalPlaceholder) whenever(view.indexOfChild(mockCommunalPlaceholder)).thenReturn(fakeViewIndex) whenever(view.context).thenReturn(context) + whenever(view.viewTreeObserver).thenReturn(mock(ViewTreeObserver::class.java)) underTest.setupCommunalHubLayout() - // Communal view added as a child of the container at the proper index, the stub is removed. - verify(view).removeView(mockCommunalPlaceholder) - verify(view).addView(eq(mockCommunalView), eq(fakeViewIndex)) + // Simluate attaching the view so flow collection starts. + val onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass( + View.OnAttachStateChangeListener::class.java + ) + verify(view, atLeast(1)).addOnAttachStateChangeListener( + onAttachStateChangeListenerArgumentCaptor.capture() + ) + for (listener in onAttachStateChangeListenerArgumentCaptor.allValues) { + listener.onViewAttachedToWindow(view) + } + testableLooper.processAllMessages() + + // Communal view added as a child of the container at the proper index. + verify(view).addView(eq(communalView), eq(fakeViewIndex)) } @Test + @RequiresFlagsDisabled(Flags.FLAG_COMMUNAL_HUB) fun doesNotSetupCommunalHubLayout_whenFlagDisabled() { whenever(mGlanceableHubContainerController.communalAvailable()) .thenReturn(MutableStateFlow(false)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index f380b6c700cd..e83a46bb56a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyevent.domain.interactor.SysUIKeyEventHandler import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.Edge import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.res.R @@ -151,7 +152,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { whenever(statusBarStateController.isDozing).thenReturn(false) mDependency.injectTestDependency(ShadeController::class.java, shadeController) whenever(dockManager.isDocked).thenReturn(false) - whenever(keyguardTransitionInteractor.transition(LOCKSCREEN, DREAMING)) + whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING))) .thenReturn(emptyFlow()) val featureFlags = FakeFeatureFlags() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt index 81d0e06df1fc..2c2fcbe75e1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.shade +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -26,8 +28,8 @@ import androidx.annotation.IdRes import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest -import com.android.systemui.Flags as AConfigFlags import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService @@ -164,10 +166,10 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSetBasedOnResource() { val headerResourceHeight = 20 val headerHelperHeight = 30 - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) .thenReturn(headerHelperHeight) overrideResource(R.bool.config_use_large_screen_shade_header, true) @@ -187,10 +189,10 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSetBasedOnHelper() { val headerResourceHeight = 20 val headerHelperHeight = 30 - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()) .thenReturn(headerHelperHeight) overrideResource(R.bool.config_use_large_screen_shade_header, true) @@ -400,8 +402,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSplitShadeLayout_isAlignedToGuideline() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) enableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd).isEqualTo(R.id.qs_edge_guideline) @@ -410,8 +412,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSinglePaneLayout_childrenHaveEqualMargins() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) disableSplitShade() underTest.updateResources() val qsStartMargin = getConstraintSetLayout(R.id.qs_frame).startMargin @@ -427,8 +429,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSplitShadeLayout_childrenHaveInsideMarginsOfZero() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) enableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endMargin).isEqualTo(0) @@ -445,9 +447,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT, FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderHeightResource() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderResourceHeight = 100 val largeScreenHeaderHelperHeight = 200 @@ -468,9 +469,9 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHeightHelper() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderResourceHeight = 100 val largeScreenHeaderHelperHeight = 200 @@ -491,8 +492,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSmallScreenLayout_qsAndNotifsTopMarginIsZero() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) setSmallScreen() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).topMargin).isEqualTo(0) @@ -512,8 +513,8 @@ class NotificationsQSContainerControllerLegacyTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun testSinglePaneShadeLayout_isAlignedToParent() { - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) disableSplitShade() underTest.updateResources() assertThat(getConstraintSetLayout(R.id.qs_frame).endToEnd) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt index 4ae751b4e7eb..f21def361e40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.shade +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -27,6 +29,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX +import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT import com.android.systemui.SysuiTestCase import com.android.systemui.fragments.FragmentHostManager import com.android.systemui.fragments.FragmentService @@ -67,6 +70,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +@EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) class NotificationsQSContainerControllerTest : SysuiTestCase() { private val view = mock<NotificationsQuickSettingsContainer>() @@ -99,7 +103,6 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) fakeSystemClock = FakeSystemClock() delayableExecutor = FakeExecutor(fakeSystemClock) - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) mContext.ensureTestableResources() whenever(view.context).thenReturn(mContext) whenever(view.resources).thenReturn(mContext.resources) @@ -161,8 +164,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreen_updateResources_refactorFlagOff_splitShadeHeightIsSet_basedOnResource() { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val helperHeight = 30 val resourceHeight = 20 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight) @@ -182,8 +185,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreen_updateResources_refactorFlagOn_splitShadeHeightIsSet_basedOnHelper() { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) val helperHeight = 30 val resourceHeight = 20 whenever(largeScreenHeaderHelper.getLargeScreenHeaderHeight()).thenReturn(helperHeight) @@ -424,8 +427,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreenLayout_refactorFlagOff_qsAndNotifsTopMarginIsOfHeaderResourceHeight() { - mSetFlagsRule.disableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderHelperHeight = 200 val largeScreenHeaderResourceHeight = 100 @@ -444,8 +447,8 @@ class NotificationsQSContainerControllerTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) fun testLargeScreenLayout_refactorFlagOn_qsAndNotifsTopMarginIsOfHeaderHelperHeight() { - mSetFlagsRule.enableFlags(FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) setLargeScreen() val largeScreenHeaderHelperHeight = 200 val largeScreenHeaderResourceHeight = 100 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 317e35c4948c..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(); @@ -216,13 +198,10 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { keyguardTransitionInteractor, () -> sceneInteractor, () -> mKosmos.getFromGoneTransitionInteractor(), + () -> mKosmos.getFromLockscreenTransitionInteractor(), () -> mKosmos.getSharedNotificationContainerInteractor(), mTestScope); - mFromLockscreenTransitionInteractor = mKosmos.getFromLockscreenTransitionInteractor(); - mFromPrimaryBouncerTransitionInteractor = - mKosmos.getFromPrimaryBouncerTransitionInteractor(); - ResourcesSplitShadeStateController splitShadeStateController = new ResourcesSplitShadeStateController(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt index 2c453a711c87..dfd7a715fcdf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplWithCoroutinesTest.kt @@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -36,6 +37,8 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isFalse() + + coroutineContext.cancelChildren() } @Test @@ -45,6 +48,8 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isTrue() + + coroutineContext.cancelChildren() } @Test @@ -58,6 +63,8 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isFalse() + + coroutineContext.cancelChildren() } @Test @@ -71,6 +78,8 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isFalse() + + coroutineContext.cancelChildren() } @Test @@ -81,5 +90,7 @@ class QuickSettingsControllerImplWithCoroutinesTest : QuickSettingsControllerImp runCurrent() assertThat(mQsController.isExpansionEnabled).isTrue() + + coroutineContext.cancelChildren() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt index 9ec9b69d44c0..05d9495db091 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.ExpandHelper import com.android.systemui.SysuiTestCase @@ -39,7 +39,7 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DragDownHelperTest : SysuiTestCase() { private lateinit var dragDownHelper: DragDownHelper diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java index bedb2b33d07b..e0eb99cebd37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java @@ -16,120 +16,161 @@ package com.android.systemui.statusbar; +import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE; + import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.Intent; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.dx.mockito.inline.extended.StaticMockitoSession; import com.android.systemui.SysuiTestCase; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.shared.recents.utilities.Utilities; +import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoSession; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; +import org.mockito.MockitoAnnotations; import org.mockito.quality.Strictness; @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutsReceiverTest extends SysuiTestCase { - @Rule public MockitoRule mockito = MockitoJUnit.rule(); + private static final Intent SHOW_INTENT = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS); + private static final Intent DISMISS_INTENT = + new Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS); + private StaticMockitoSession mockitoSession; private KeyboardShortcutsReceiver mKeyboardShortcutsReceiver; - private Intent mIntent; - private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); + private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); @Mock private KeyboardShortcuts mKeyboardShortcuts; @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch; @Before public void setUp() { - mIntent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS); + MockitoAnnotations.initMocks(this); + mSetFlagsRule.disableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); mKeyboardShortcuts.mContext = mContext; mKeyboardShortcutListSearch.mContext = mContext; KeyboardShortcuts.sInstance = mKeyboardShortcuts; KeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch; + + mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); + } + + @Before + public void startStaticMocking() { + mockitoSession = + ExtendedMockito.mockitoSession() + .spyStatic(Utilities.class) + .strictness(Strictness.LENIENT) + .startMocking(); + } + + @After + public void endStaticMocking() { + mockitoSession.finishMocking(); } @Test public void onReceive_whenFlagOffDeviceIsTablet_showKeyboardShortcuts() { - MockitoSession mockitoSession = ExtendedMockito.mockitoSession() - .spyStatic(Utilities.class) - .strictness(Strictness.LENIENT) - .startMocking(); mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false); - mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); when(Utilities.isLargeScreen(mContext)).thenReturn(true); - mKeyboardShortcutsReceiver.onReceive(mContext, mIntent); + mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT); verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt()); verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt()); - mockitoSession.finishMocking(); } @Test public void onReceive_whenFlagOffDeviceIsNotTablet_showKeyboardShortcuts() { - MockitoSession mockitoSession = ExtendedMockito.mockitoSession() - .spyStatic(Utilities.class) - .strictness(Strictness.LENIENT) - .startMocking(); mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false); - mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); when(Utilities.isLargeScreen(mContext)).thenReturn(false); - mKeyboardShortcutsReceiver.onReceive(mContext, mIntent); + mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT); verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt()); verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt()); - mockitoSession.finishMocking(); } @Test public void onReceive_whenFlagOnDeviceIsTablet_showKeyboardShortcutListSearch() { - MockitoSession mockitoSession = ExtendedMockito.mockitoSession() - .spyStatic(Utilities.class) - .strictness(Strictness.LENIENT) - .startMocking(); mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true); - mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); when(Utilities.isLargeScreen(mContext)).thenReturn(true); - mKeyboardShortcutsReceiver.onReceive(mContext, mIntent); + mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT); verify(mKeyboardShortcuts, never()).showKeyboardShortcuts(anyInt()); verify(mKeyboardShortcutListSearch).showKeyboardShortcuts(anyInt()); - mockitoSession.finishMocking(); } @Test public void onReceive_whenFlagOnDeviceIsNotTablet_showKeyboardShortcuts() { - MockitoSession mockitoSession = ExtendedMockito.mockitoSession() - .spyStatic(Utilities.class) - .strictness(Strictness.LENIENT) - .startMocking(); mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true); - mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags)); when(Utilities.isLargeScreen(mContext)).thenReturn(false); - mKeyboardShortcutsReceiver.onReceive(mContext, mIntent); + mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT); verify(mKeyboardShortcuts).showKeyboardShortcuts(anyInt()); verify(mKeyboardShortcutListSearch, never()).showKeyboardShortcuts(anyInt()); - mockitoSession.finishMocking(); + } + + @Test + public void onShowIntent_rewriteFlagOn_oldFlagOn_isLargeScreen_doesNotLaunchOldVersions() { + mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true); + mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); + when(Utilities.isLargeScreen(mContext)).thenReturn(true); + + mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT); + + verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch); + } + + @Test + public void onShowIntent_rewriteFlagOn_oldFlagOn_isSmallScreen_doesNotLaunchOldVersions() { + mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true); + mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); + when(Utilities.isLargeScreen(mContext)).thenReturn(false); + + mKeyboardShortcutsReceiver.onReceive(mContext, SHOW_INTENT); + + verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch); + } + + @Test + public void onDismissIntent_rewriteFlagOn_oldFlagOn_isLargeScreen_doesNotDismissOldVersions() { + mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true); + mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); + when(Utilities.isLargeScreen(mContext)).thenReturn(true); + + mKeyboardShortcutsReceiver.onReceive(mContext, DISMISS_INTENT); + + verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch); + } + + @Test + public void onDismissIntent_rewriteFlagOn_oldFlagOn_isSmallScreen_doesNotDismissOldVersions() { + mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, true); + mSetFlagsRule.enableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE); + when(Utilities.isLargeScreen(mContext)).thenReturn(false); + + mKeyboardShortcutsReceiver.onReceive(mContext, DISMISS_INTENT); + + verifyZeroInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 1504d4c1f033..995b5383c3c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -65,9 +65,9 @@ import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricSourceType; import android.os.BatteryManager; import android.os.RemoteException; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; @@ -88,7 +88,7 @@ import java.util.List; import java.util.Set; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardIndicationControllerTest extends KeyguardIndicationControllerBaseTest { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt index cdc752098aa7..4a14f8853904 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED import kotlinx.coroutines.Dispatchers @@ -28,7 +28,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.times import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) class KeyguardIndicationControllerWithCoroutinesTest : KeyguardIndicationControllerBaseTest() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt index 8cb530c355bd..948a73208d10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt @@ -1,7 +1,7 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.util.DisplayMetrics +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer @@ -16,7 +16,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LSShadeTransitionLoggerTest : SysuiTestCase() { lateinit var logger: LSShadeTransitionLogger @@ -41,4 +41,4 @@ class LSShadeTransitionLoggerTest : SysuiTestCase() { // log a non-null, non row, ensure no crash logger.logDragDownStarted(view) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt index d3befb4ad4cd..fe2dd6d78a82 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith import java.util.function.Consumer -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LightRevealScrimTest : SysuiTestCase() { @@ -85,4 +85,4 @@ class LightRevealScrimTest : SysuiTestCase() { private const val DEFAULT_WIDTH = 42 private const val DEFAULT_HEIGHT = 24 } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt index 402d9aab66bd..e48242a3e003 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeQsTransitionControllerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class LockscreenShadeQsTransitionControllerTest : SysuiTestCase() { private val configurationController = FakeConfigurationController() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index a92cf8c96339..69e8f4737a5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.statusbar import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.ExpandHelper import com.android.systemui.SysUITestModule @@ -74,7 +74,7 @@ private fun <T> anyObject(): T { @SmallTest @RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class LockscreenShadeTransitionControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java index d3febf55117b..ef1c927f22d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationListenerTest.java @@ -32,8 +32,8 @@ import android.os.UserHandle; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationListenerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java index d3850be7c192..c9d910c530ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java @@ -27,9 +27,9 @@ import android.app.Notification; import android.content.Context; import android.os.SystemClock; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationRemoteInputManagerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index fc0c85e30d5a..9f94cff4ead4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -17,11 +17,11 @@ package com.android.systemui.statusbar import android.os.IBinder -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Choreographer import android.view.View import android.view.ViewRootImpl +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ShadeInterpolation @@ -59,7 +59,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @SmallTest class NotificationShadeDepthControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt index 49e5c456e645..9907740672ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -43,7 +43,7 @@ import org.mockito.Mockito.`when` as whenever @SmallTest @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class PulseExpansionHandlerTest : SysuiTestCase() { private lateinit var pulseExpansionHandler: PulseExpansionHandler diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java index ce11d6a62a8c..58943ea3b4ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/RemoteInputNotificationRebuilderTest.java @@ -27,9 +27,9 @@ import android.app.RemoteInputHistoryItem; import android.net.Uri; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -44,7 +44,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class RemoteInputNotificationRebuilderTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt index 2606be5fabad..6b9a19a92fc4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt @@ -1,7 +1,7 @@ package com.android.systemui.statusbar import org.mockito.Mockito.`when` as whenever -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController @@ -14,7 +14,7 @@ import org.mockito.Mockito.intThat import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class SingleShadeLockScreenOverScrollerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java index 775dc3c95746..3346e19b4ce9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java @@ -29,9 +29,9 @@ import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -52,7 +52,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class SmartReplyControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt index 700fb1ec332c..58473c4e07a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt @@ -1,7 +1,7 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -23,7 +23,7 @@ import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt index 79a2008e7542..26692c908c5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Assert.assertEquals @@ -24,7 +24,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class StatusBarStateEventTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt index b90582575970..78c1887f7c0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt @@ -5,9 +5,9 @@ import android.os.UserHandle import android.os.VibrationAttributes import android.os.VibrationEffect import android.os.Vibrator -import android.testing.AndroidTestingRunner import android.view.HapticFeedbackConstants import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq @@ -26,7 +26,7 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import java.util.concurrent.Executor -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class VibratorHelperTest : SysuiTestCase() { @@ -120,4 +120,4 @@ class VibratorHelperTest : SysuiTestCase() { return verify(vibrator) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt index f0a457e2c19f..7d2b463afab7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallBackgroundContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipBackgroundContainerTest.kt @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone.ongoingcall +package com.android.systemui.statusbar.chips.ui.view import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater import android.view.View import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -31,16 +31,17 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper -class OngoingCallBackgroundContainerTest : SysuiTestCase() { +class ChipBackgroundContainerTest : SysuiTestCase() { - private lateinit var underTest: OngoingCallBackgroundContainer + private lateinit var underTest: ChipBackgroundContainer @Before fun setUp() { allowTestableLooperAsMainThread() TestableLooper.get(this).runWithLooper { - val chipView = LayoutInflater.from(context).inflate(R.layout.ongoing_call_chip, null) - underTest = chipView.requireViewById(R.id.ongoing_call_chip_background) + val chipView = + LayoutInflater.from(context).inflate(R.layout.ongoing_activity_chip, null) + underTest = chipView.requireViewById(R.id.ongoing_activity_chip_background) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt index 7e25aa373097..b8d4e47544dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/chips/ui/view/ChipChronometerTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * 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. @@ -14,15 +14,15 @@ * limitations under the License. */ -package com.android.systemui.statusbar.phone.ongoingcall +package com.android.systemui.statusbar.chips.ui.view import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater import android.view.View import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase +import com.android.systemui.res.R import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test @@ -38,17 +38,18 @@ private const val XL_TEXT = "00:0000" @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper -class OngoingCallChronometerTest : SysuiTestCase() { +class ChipChronometerTest : SysuiTestCase() { - private lateinit var textView: OngoingCallChronometer + private lateinit var textView: ChipChronometer private lateinit var doesNotFitText: String @Before fun setUp() { allowTestableLooperAsMainThread() TestableLooper.get(this).runWithLooper { - val chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null) - textView = chipView.findViewById(R.id.ongoing_call_chip_time)!! + val chipView = + LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null) + textView = chipView.findViewById(R.id.ongoing_activity_chip_time)!! measureTextView() calculateDoesNotFixText() } @@ -159,8 +160,8 @@ class OngoingCallChronometerTest : SysuiTestCase() { private fun measureTextView() { textView.measure( - View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + View.MeasureSpec.makeMeasureSpec(TEXT_VIEW_MAX_WIDTH, View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt index 7e88ae080178..643acdbb9277 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.connectivity import android.os.UserManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.lifecycle.Lifecycle import com.android.systemui.SysuiTestCase @@ -42,7 +42,7 @@ import org.mockito.MockitoAnnotations import java.util.concurrent.Executor @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class AccessPointControllerImplTest : SysuiTestCase() { @@ -244,4 +244,4 @@ class AccessPointControllerImplTest : SysuiTestCase() { verify(wifiEntryOther).connect(any()) verify(callback, never()).onSettingsActivityTriggered(any()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt index 7aed4f7e250b..40f81e2bae5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/MobileStateTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.statusbar.connectivity -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase @@ -26,7 +26,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MobileStateTest : SysuiTestCase() { private val state = MobileState() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index 461d80412cb5..4241254e1fb9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -39,10 +39,10 @@ import android.os.Looper; import android.telephony.NetworkRegistrationInfo; import android.telephony.ServiceState; import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.SignalIcon.MobileIconGroup; @@ -60,7 +60,7 @@ import org.junit.runner.RunWith; import java.util.HashMap; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class NetworkControllerDataTest extends NetworkControllerBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java index 3bbf06dbc69c..521cb4f4c42d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerEthernetTest.java @@ -19,9 +19,9 @@ package com.android.systemui.statusbar.connectivity; import static junit.framework.Assert.assertEquals; import android.net.NetworkCapabilities; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import org.junit.Test; @@ -30,7 +30,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class NetworkControllerEthernetTest extends NetworkControllerBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java index 35609a5faf00..22f0e9d20c41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java @@ -33,10 +33,10 @@ import android.telephony.SignalStrength; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.graph.SignalDrawable; @@ -59,7 +59,7 @@ import java.util.Collections; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class NetworkControllerSignalTest extends NetworkControllerBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java index 44a1c50e5a58..6c80a97625a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerWifiTest.java @@ -34,9 +34,9 @@ import android.net.NetworkInfo; import android.net.vcn.VcnTransportInfo; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.mobile.TelephonyIcons; @@ -50,7 +50,7 @@ import org.mockito.Mockito; import java.util.Collections; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class NetworkControllerWifiTest extends NetworkControllerBaseTest { // These match the constants in WifiManager and need to be kept up to date. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt index 5bf0a94935cf..3eeed73a539c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkTypeResIdCacheTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.connectivity -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.systemui.SysuiTestCase @@ -26,7 +26,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NetworkTypeResIdCacheTest : SysuiTestCase() { private lateinit var cache: NetworkTypeResIdCache private var overrides = MobileIconCarrierIdOverridesFake() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt index 452302d4db8a..984bda1c0d21 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerTest.kt @@ -19,11 +19,11 @@ package com.android.systemui.statusbar.events import android.content.Context import android.graphics.Insets import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Gravity import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule @@ -46,7 +46,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SystemEventChipAnimationControllerTest : SysuiTestCase() { private lateinit var controller: SystemEventChipAnimationController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt index ae84df55e113..742494b769de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.events -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor @@ -40,7 +40,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest @OptIn(ExperimentalCoroutinesApi::class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index cacfa8dc0be4..376873d19624 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.statusbar.events import android.graphics.Insets import android.graphics.Rect import android.os.Process -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule @@ -54,7 +54,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @OptIn(ExperimentalCoroutinesApi::class) @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt index d3f5adeb05bb..0f58990d4310 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.statusbar.gesture -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.InputEvent import android.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeDisplayTracker @@ -13,7 +13,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class GenericGestureDetectorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java index 6b2ee76a75ac..01a0fd020bda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java @@ -19,11 +19,11 @@ package com.android.systemui.statusbar.notification; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.widget.FrameLayout; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -36,7 +36,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class AboveShelfObserverTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java index fc4702c209e1..d66b010daefd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AssistantFeedbackControllerTest.java @@ -42,9 +42,9 @@ import android.os.Handler; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -58,7 +58,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class AssistantFeedbackControllerTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java index 0103564088e0..77fd06757595 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicChildBindControllerTest.java @@ -25,12 +25,12 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -56,7 +56,7 @@ import java.util.List; import java.util.Map; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class DynamicChildBindControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java index 5b72ca07edbe..d879fcecffab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java @@ -25,9 +25,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -38,9 +38,10 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; @SmallTest -@org.junit.runner.RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class DynamicPrivacyControllerTest extends SysuiTestCase { @@ -127,4 +128,4 @@ public class DynamicPrivacyControllerTest extends SysuiTestCase { mDynamicPrivacyController.onUnlockedChanged(); verifyNoMoreInteractions(mListener); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt index 1cce3b5b9e77..9e733be6665c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt @@ -16,15 +16,17 @@ package com.android.systemui.statusbar.notification +import android.platform.test.annotations.DisableFlags import android.provider.DeviceConfig import android.provider.Settings -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.shared.PriorityPeopleSection import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.Utils import com.android.systemui.util.mockito.any @@ -39,8 +41,9 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoSession import org.mockito.quality.Strictness -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest +@DisableFlags(PriorityPeopleSection.FLAG_NAME) // this class has no logic with the flag enabled class NotificationSectionsFeatureManagerTest : SysuiTestCase() { var manager: NotificationSectionsFeatureManager? = null val proxyFake = DeviceConfigProxyFake() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt index 3b3f05d25c16..3abdf6212f22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.statusbar.notification import android.app.Notification.GROUP_ALERT_SUMMARY -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -35,7 +35,7 @@ import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationTransitionAnimatorControllerTest : SysuiTestCase() { @Mock lateinit var notificationListContainer: NotificationListContainer diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt index 1aac515f538b..a5206f52b6f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer @@ -28,7 +28,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class NotificationWakeUpCoordinatorLoggerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index 67b540cd762e..0906d8eadf44 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase @@ -60,7 +60,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) class NotificationWakeUpCoordinatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt index 7d8cf3657ba1..382b307fb9de 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification import android.platform.test.annotations.EnableFlags import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.shared.NotificationsImprovedHunAnimation @@ -10,13 +11,12 @@ import com.android.systemui.util.mockito.whenever import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.times import org.mockito.Mockito.verify @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class RoundableTest : SysuiTestCase() { private val targetView: View = mock() private val roundable = FakeRoundable(targetView = targetView) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java index 2d044fec1eb6..8e95ac599ce1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java @@ -30,8 +30,8 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -51,7 +51,7 @@ import java.util.Arrays; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class HighPriorityProviderTest extends SysuiTestCase { @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier; @Mock private GroupMembershipManager mGroupMembershipManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt index 892575ab6c71..2a587511eaec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.collection -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.lifecycle.Observer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -34,7 +34,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifLiveDataImplTest : SysuiTestCase() { @@ -164,4 +164,4 @@ class NotifLiveDataImplTest : SysuiTestCase() { assertThat(executor.runAllReady()).isEqualTo(2) verifyNoMoreInteractions(syncObserver, asyncObserver) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt index 9c8ac5cded9e..d87f827de776 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.collection -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -30,7 +30,7 @@ import org.junit.runner.RunWith import java.lang.UnsupportedOperationException @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifLiveDataStoreImplTest : SysuiTestCase() { @@ -102,4 +102,4 @@ class NotifLiveDataStoreImplTest : SysuiTestCase() { liveDataStoreImpl.setActiveNotifList(mutableListOf(entry1, entry2)) executor.runAllReady() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt index 3b908b4175f9..f1da22f08e75 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.collection -import android.testing.AndroidTestingRunner import android.view.Choreographer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dagger.SysUISingleton @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotifPipelineChoreographerTest : SysuiTestCase() { val viewChoreographer: Choreographer = mock() @@ -118,4 +118,4 @@ interface NotifPipelineChoreographerTestComponent { @BindsInstance @Main executor: DelayableExecutor ): NotifPipelineChoreographerTestComponent } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java index 8a48fe10d7fc..72d1db3affe8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java @@ -46,8 +46,8 @@ import android.os.UserHandle; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -64,7 +64,7 @@ import org.mockito.Mockito; import java.util.ArrayList; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationEntryTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test"; private static final int TEST_UID = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt index ab55a7d650c6..1fd6b042ad67 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/SectionStyleProviderTest.kt @@ -20,7 +20,7 @@ import android.os.UserHandle import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection @@ -47,7 +47,7 @@ import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SectionStyleProviderTest : SysuiTestCase() { @Rule @JvmField public val setFlagsRule = SetFlagsRule() @@ -118,4 +118,4 @@ class SectionStyleProviderTest : SysuiTestCase() { override fun getSection(): NotifSection? = NotifSection(inputSectioner, 1) } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt index 4708350c1c0a..2ad3c9e1c21a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt @@ -25,7 +25,7 @@ import android.os.Bundle import android.os.UserHandle import android.service.notification.NotificationListenerService.Ranking import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection @@ -48,7 +48,7 @@ private const val PACKAGE = "pkg" private const val USER_ID = -1 @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class TargetSdkResolverTest : SysuiTestCase() { private val packageManager: PackageManager = mock() private val applicationInfo = ApplicationInfo().apply { targetSdkVersion = SDK_VERSION } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java index b1180aebe9bd..f029a2ca6099 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java @@ -30,8 +30,8 @@ import static java.util.Objects.requireNonNull; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -57,7 +57,7 @@ import java.util.Arrays; import java.util.Collections; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class GroupCoalescerTest extends SysuiTestCase { private GroupCoalescer mCoalescer; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java index f2207afeda10..1f2925528077 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java @@ -30,9 +30,9 @@ import android.app.Person; import android.content.Intent; import android.graphics.Color; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -47,7 +47,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ColorizedFgsCoordinatorTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt index 59fc591e4d06..e72109d4d8e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder @@ -39,7 +39,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class DataStoreCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: DataStoreCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt index f91e5a8cf626..543f0c72a8ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DismissibilityCoordinatorTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -38,7 +38,7 @@ import org.mockito.Mockito import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DismissibilityCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: DismissibilityCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt index a544cad03b77..4d5ea92aa0e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DreamCoordinatorTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository @@ -49,7 +49,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DreamCoordinatorTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController @Mock private lateinit var notifPipeline: NotifPipeline diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt index 929c3d4288d4..7b688d4f91fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class GroupCountCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: GroupCountCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt index eac0e296c51f..3f140265e5f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupWhenCoordinatorTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.SbnBuilder @@ -45,7 +45,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class GroupWhenCoordinatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt index a652ad64ea8d..7fe97d27f273 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -42,7 +42,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class GutsCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: GutsCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt index cd75e0811fff..8e9323fead92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification.GROUP_ALERT_ALL import android.app.Notification.GROUP_ALERT_SUMMARY -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -70,7 +70,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class HeadsUpCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: HeadsUpCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java index 27542a462d36..5dcad4bb9e62 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java @@ -24,9 +24,9 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.util.SparseArray; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -47,7 +47,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class HideNotifsForOtherUsersCoordinatorTest extends SysuiTestCase { @Mock private NotificationLockscreenUserManager mLockscreenUserManager; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index 5ff73538b359..25533d82608b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -20,7 +20,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.app.Notification import android.os.UserHandle import android.provider.Settings -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -67,7 +67,7 @@ import kotlin.time.Duration.Companion.seconds import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class KeyguardCoordinatorTest : SysuiTestCase() { private val headsUpManager: HeadsUpManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java index e90a3ac8bfdc..07c29a024a6c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java @@ -33,8 +33,8 @@ import android.media.session.MediaSession; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.service.notification.NotificationListenerService; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -58,7 +58,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public final class MediaCoordinatorTest extends SysuiTestCase { private MediaSession mMediaSession; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt index c29ff416feb9..501bca248c76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/NotificationStatsLoggerCoordinatorTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.platform.test.annotations.EnableFlags import android.service.notification.NotificationListenerService.REASON_CANCEL -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotifPipeline @@ -36,7 +36,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) class NotificationStatsLoggerCoordinatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index cceaaea672c4..80127682be02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -39,10 +39,10 @@ import static java.util.Objects.requireNonNull; import android.database.ContentObserver; import android.os.Handler; import android.os.RemoteException; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -88,7 +88,7 @@ import java.util.List; import java.util.Map; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class PreparationCoordinatorTest extends SysuiTestCase { private NotifCollectionListener mCollectionListener; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java index 3d1253e2b05d..c05b13163d32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java @@ -35,9 +35,9 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.testing.AndroidTestingRunner; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -67,7 +67,7 @@ import java.util.ArrayList; import java.util.Arrays; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class RankingCoordinatorTest extends SysuiTestCase { @Mock private StatusBarStateController mStatusBarStateController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt index d3df48e9ef02..deb3fc1224ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt @@ -23,8 +23,8 @@ import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -54,7 +54,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class RemoteInputCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: RemoteInputCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt index 7daadb07f89a..1b7ec5381713 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAlertTimeCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder @@ -37,7 +37,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class RowAlertTimeCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: RowAlertTimeCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt index a66f8ce1a92c..5b231e21e700 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.AssistantFeedbackController @@ -41,7 +41,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class RowAppearanceCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: RowAppearanceCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt index 56f16f32ec15..ccf7cdd70675 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.service.notification.NotificationListenerService.REASON_APP_CANCEL import android.service.notification.NotificationListenerService.REASON_CANCEL -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -40,7 +40,7 @@ import java.util.concurrent.Executor import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class ShadeEventCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: ShadeEventCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index ea4f692ef4f1..c7513de7a41a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.coordinator import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX @@ -51,7 +51,7 @@ import org.mockito.MockitoAnnotations.initMocks import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class StackCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: StackCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt index b1d2ea21f7fc..c8fbe61fa799 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback @@ -40,7 +40,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class ViewConfigCoordinatorTest : SysuiTestCase() { private lateinit var coordinator: ViewConfigCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt index 8e6ceccbc14e..7943872558c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt @@ -20,8 +20,8 @@ import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING import com.android.systemui.SysuiTestCase @@ -54,7 +54,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifUiAdjustmentProviderTest : SysuiTestCase() { private val lockscreenUserManager: NotificationLockscreenUserManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt index 1cdd023dd01c..d2057703d560 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/SemiStableSortTest.kt @@ -15,9 +15,9 @@ */ package com.android.systemui.statusbar.notification.collection.listbuilder -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Assert.assertFalse @@ -27,7 +27,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class SemiStableSortTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt index 20369546d68a..49f836fe9f81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderHelperTest.kt @@ -15,8 +15,8 @@ */ package com.android.systemui.statusbar.notification.collection.listbuilder -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.listbuilder.ShadeListBuilderHelper.getContiguousSubLists @@ -25,7 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class ShadeListBuilderHelperTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt index 22f6bdc54b85..341a51e32a46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection import android.service.notification.NotificationListenerService.RankingMap -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -34,7 +34,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotifCollectionInconsistencyTrackerTest : SysuiTestCase() { private val logger = spy(NotifCollectionLogger(logcatLogBuffer())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt index a09f3a35c308..99e55a85344a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.collection.notifcollection import android.os.Handler -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -41,7 +41,7 @@ import java.util.function.Consumer import java.util.function.Predicate @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class SelfTrackingLifetimeExtenderTest : SysuiTestCase() { private lateinit var extender: TestableSelfTrackingLifetimeExtender diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt index b56f8e9364c4..586b947d5299 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt @@ -15,7 +15,7 @@ */ package com.android.systemui.statusbar.notification.collection.provider -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock @@ -29,7 +29,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class VisualStabilityProviderTest : SysuiTestCase() { private val visualStabilityProvider = VisualStabilityProvider() private val listener: OnReorderingAllowedListener = mock() @@ -148,4 +148,4 @@ class VisualStabilityProviderTest : SysuiTestCase() { visualStabilityProvider.isReorderingAllowed = true verify(selfAddingListener, times(2)).onReorderingAllowed() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt index eeabc744987b..9d3e2e8dfcb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.notification.collection.render import android.content.Context -import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer @@ -34,7 +34,7 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ShadeViewDifferTest : SysuiTestCase() { private lateinit var differ: ShadeViewDiffer private val rootController = FakeController(mContext, "RootController") diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt index 2a3c1a53559e..39085295fbac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/domain/interactor/SeenNotificationsInteractorTest.kt @@ -15,7 +15,7 @@ package com.android.systemui.statusbar.notification.domain.interactor -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -25,7 +25,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class SeenNotificationsInteractorTest : SysuiTestCase() { 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 158f38d48197..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 @@ -19,12 +19,13 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings -import android.testing.AndroidTestingRunner 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.power.data.repository.powerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.res.R -import com.android.systemui.shade.data.repository.shadeRepository +import com.android.systemui.shade.shadeTestUtil import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository @@ -45,13 +46,16 @@ 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 +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters -@RunWith(AndroidTestingRunner::class) +@RunWith(ParameterizedAndroidJunit4::class) @SmallTest @EnableFlags(FooterViewRefactor.FLAG_NAME) -class FooterViewModelTest : SysuiTestCase() { +class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } @@ -59,11 +63,29 @@ class FooterViewModelTest : SysuiTestCase() { private val testScope = kosmos.testScope private val activeNotificationListRepository = kosmos.activeNotificationListRepository private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository - private val shadeRepository = kosmos.shadeRepository private val powerRepository = kosmos.powerRepository private val fakeSecureSettingsRepository = kosmos.fakeSecureSettingsRepository - val underTest = kosmos.footerViewModel + private val shadeTestUtil by lazy { kosmos.shadeTestUtil } + + private lateinit var underTest: FooterViewModel + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf().andSceneContainer() + } + } + + init { + mSetFlagsRule.setFlagsParameterization(flags) + } + + @Before + fun setup() { + underTest = kosmos.footerViewModel + } @Test fun messageVisible_whenFilteredNotifications() = @@ -146,11 +168,9 @@ class FooterViewModelTest : SysuiTestCase() { val visible by collectLastValue(underTest.clearAllButton.isVisible) runCurrent() - // WHEN shade is expanded + // WHEN shade is expanded AND QS not expanded fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - shadeRepository.setLegacyShadeExpansion(1f) - // AND QS not expanded - shadeRepository.setQsExpansion(0f) + shadeTestUtil.setShadeAndQsExpansion(1f, 0f) // AND device is awake powerRepository.updateWakefulness( rawState = WakefulnessState.AWAKE, @@ -182,9 +202,9 @@ class FooterViewModelTest : SysuiTestCase() { // WHEN shade is collapsed fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE) - shadeRepository.setLegacyShadeExpansion(0f) + shadeTestUtil.setShadeExpansion(0f) // AND QS not expanded - shadeRepository.setQsExpansion(0f) + shadeTestUtil.setQsExpansion(0f) // AND device is awake powerRepository.updateWakefulness( rawState = WakefulnessState.AWAKE, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt index 4ac9dc2be161..bfa816e65eb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt @@ -30,8 +30,8 @@ import android.graphics.drawable.Icon import android.os.Bundle import android.os.SystemClock import android.os.UserHandle -import android.testing.AndroidTestingRunner import androidx.test.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any @@ -53,7 +53,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class IconManagerTest : SysuiTestCase() { companion object { private const val TEST_PACKAGE_NAME = "test" diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt index b410b33b97d9..c9f2addfd39b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt @@ -15,7 +15,7 @@ package com.android.systemui.statusbar.notification.icon.domain.interactor -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule @@ -52,7 +52,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationIconsInteractorTest : SysuiTestCase() { private val bubbles: Bubbles = mock() @@ -151,7 +151,7 @@ class NotificationIconsInteractorTest : SysuiTestCase() { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { private val bubbles: Bubbles = mock() @@ -256,7 +256,7 @@ class AlwaysOnDisplayNotificationIconsInteractorTest : SysuiTestCase() { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class StatusBarNotificationIconsInteractorTest : SysuiTestCase() { private val bubbles: Bubbles = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java index 60eea9beb2e0..af2789b8401a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java @@ -26,9 +26,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import androidx.core.os.CancellationSignal; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.NotificationMessagingUtil; @@ -47,7 +47,7 @@ import org.mockito.MockitoAnnotations; import java.util.concurrent.atomic.AtomicReference; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class HeadsUpViewBinderTest extends SysuiTestCase { private HeadsUpViewBinder mViewBinder; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index 86620480fa7b..19214fb831eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -42,9 +42,9 @@ import android.content.Context; import android.os.Handler; import android.os.UserHandle; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; @@ -86,7 +86,7 @@ import java.util.Map; import java.util.function.Consumer; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { private static final int NOTIF_USER_ID = 0; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java index 7ade053720e9..3e8461a225b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java @@ -60,8 +60,8 @@ import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.testing.UiEventLoggerFake; @@ -96,7 +96,7 @@ import java.util.Set; * Tests for the interruption state provider which understands whether the system & notification * is in a state allowing a particular notification to hun, pulse, or bubble. */ -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class NotificationInterruptStateProviderImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index 7ed33126a54f..a6177e8feb1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.interruption import android.platform.test.annotations.DisableFlags -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision @@ -34,7 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() { override val provider by lazy { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index edab9d9f7fdf..eeb51a684d42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -16,11 +16,13 @@ package com.android.systemui.statusbar.notification.interruption +import android.Manifest.permission import android.app.Notification.CATEGORY_EVENT import android.app.Notification.CATEGORY_REMINDER import android.app.NotificationManager +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE @@ -28,9 +30,11 @@ import com.android.systemui.statusbar.notification.interruption.VisualInterrupti import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.anyString +import org.mockito.Mockito.`when` @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() { override val provider by lazy { @@ -51,7 +55,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro uiEventLogger, userTracker, avalancheProvider, - systemSettings + systemSettings, + packageManager ) } @@ -83,14 +88,18 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro fun testAvalancheFilter_duringAvalanche_allowConversationFromAfterEvent() { avalancheProvider.startTime = whenAgo(10) - withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) { + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { ensurePeekState() - assertShouldHeadsUp(buildEntry { - importance = NotificationManager.IMPORTANCE_HIGH - isConversation = true - isImportantConversation = false - whenMs = whenAgo(5) - }) + assertShouldHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + isConversation = true + isImportantConversation = false + whenMs = whenAgo(5) + } + ) } } @@ -98,14 +107,18 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro fun testAvalancheFilter_duringAvalanche_suppressConversationFromBeforeEvent() { avalancheProvider.startTime = whenAgo(10) - withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) { + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { ensurePeekState() - assertShouldNotHeadsUp(buildEntry { - importance = NotificationManager.IMPORTANCE_DEFAULT - isConversation = true - isImportantConversation = false - whenMs = whenAgo(15) - }) + assertShouldNotHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_DEFAULT + isConversation = true + isImportantConversation = false + whenMs = whenAgo(15) + } + ) } } @@ -113,12 +126,16 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro fun testAvalancheFilter_duringAvalanche_allowHighPriorityConversation() { avalancheProvider.startTime = whenAgo(10) - withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) { + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { ensurePeekState() - assertShouldHeadsUp(buildEntry { - importance = NotificationManager.IMPORTANCE_HIGH - isImportantConversation = true - }) + assertShouldHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + isImportantConversation = true + } + ) } } @@ -126,12 +143,16 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro fun testAvalancheFilter_duringAvalanche_allowCall() { avalancheProvider.startTime = whenAgo(10) - withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) { + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { ensurePeekState() - assertShouldHeadsUp(buildEntry { - importance = NotificationManager.IMPORTANCE_HIGH - isCall = true - }) + assertShouldHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + isCall = true + } + ) } } @@ -139,12 +160,16 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro fun testAvalancheFilter_duringAvalanche_allowCategoryReminder() { avalancheProvider.startTime = whenAgo(10) - withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) { + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { ensurePeekState() - assertShouldHeadsUp(buildEntry { - importance = NotificationManager.IMPORTANCE_HIGH - category = CATEGORY_REMINDER - }) + assertShouldHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + category = CATEGORY_REMINDER + } + ) } } @@ -152,12 +177,16 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro fun testAvalancheFilter_duringAvalanche_allowCategoryEvent() { avalancheProvider.startTime = whenAgo(10) - withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) { + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { ensurePeekState() - assertShouldHeadsUp(buildEntry { - importance = NotificationManager.IMPORTANCE_HIGH - category = CATEGORY_EVENT - }) + assertShouldHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + category = CATEGORY_EVENT + } + ) } } @@ -165,7 +194,9 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro fun testAvalancheFilter_duringAvalanche_allowFsi() { avalancheProvider.startTime = whenAgo(10) - withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) { + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { assertFsiNotSuppressed() } } @@ -174,16 +205,44 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro fun testAvalancheFilter_duringAvalanche_allowColorized() { avalancheProvider.startTime = whenAgo(10) - withFilter(AvalancheSuppressor(avalancheProvider, systemClock, systemSettings)) { + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { ensurePeekState() - assertShouldHeadsUp(buildEntry { - importance = NotificationManager.IMPORTANCE_HIGH - isColorized = true - }) + assertShouldHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + isColorized = true + } + ) } } @Test + fun testAvalancheFilter_duringAvalanche_allowEmergency() { + avalancheProvider.startTime = whenAgo(10) + + `when`( + packageManager.checkPermission( + org.mockito.Mockito.eq(permission.RECEIVE_EMERGENCY_BROADCAST), + anyString() + ) + ).thenReturn(PERMISSION_GRANTED) + + withFilter( + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + ) { + ensurePeekState() + assertShouldHeadsUp( + buildEntry { + importance = NotificationManager.IMPORTANCE_HIGH + } + ) + } + } + + + @Test fun testPeekCondition_suppressesOnlyPeek() { withCondition(TestCondition(types = setOf(PEEK)) { true }) { assertPeekSuppressed() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt index 3b979a7c1386..71e7dc522e20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt @@ -42,6 +42,7 @@ import android.app.PendingIntent import android.app.PendingIntent.FLAG_MUTABLE import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.content.pm.UserInfo import android.graphics.drawable.Icon import android.hardware.display.FakeAmbientDisplayConfiguration @@ -129,6 +130,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() { protected val userTracker = FakeUserTracker() protected val avalancheProvider: AvalancheProvider = mock() lateinit var systemSettings: SystemSettings + protected val packageManager: PackageManager = mock() protected abstract val provider: VisualInterruptionDecisionProvider diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt index 60aaa646fced..61c008b55ad0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt @@ -15,11 +15,11 @@ */ package com.android.systemui.statusbar.notification.interruption +import android.content.pm.PackageManager import android.hardware.display.AmbientDisplayConfiguration import android.os.Handler import android.os.PowerManager import com.android.internal.logging.UiEventLogger -import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker @@ -53,7 +53,8 @@ object VisualInterruptionDecisionProviderTestUtil { uiEventLogger: UiEventLogger, userTracker: UserTracker, avalancheProvider: AvalancheProvider, - systemSettings: SystemSettings + systemSettings: SystemSettings, + packageManager: PackageManager, ): VisualInterruptionDecisionProvider { return if (VisualInterruptionRefactor.isEnabled) { VisualInterruptionDecisionProviderImpl( @@ -73,7 +74,8 @@ object VisualInterruptionDecisionProviderTestUtil { uiEventLogger, userTracker, avalancheProvider, - systemSettings + systemSettings, + packageManager ) } else { NotificationInterruptStateProviderWrapper( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java index 2662c80dce1d..59741718476f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/ExpansionStateLoggerTest.java @@ -22,9 +22,9 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.os.RemoteException; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -44,7 +44,7 @@ import org.mockito.MockitoAnnotations; import java.util.Collections; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ExpansionStateLoggerTest extends SysuiTestCase { private static final String NOTIFICATION_KEY = "notin_key"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java index 111309112f61..a8929a63a812 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java @@ -35,9 +35,9 @@ import android.os.Handler; import android.os.Looper; import android.os.UserHandle; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; @@ -87,7 +87,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executor; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) public class NotificationLoggerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt index 4b0b4b89fad4..3ea7732bdf15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt @@ -21,8 +21,8 @@ import android.app.StatsManager import android.graphics.Bitmap import android.graphics.drawable.Icon import android.stats.sysui.NotificationEnums -import android.testing.AndroidTestingRunner import android.util.StatsEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.assertLogsWtf @@ -46,7 +46,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationMemoryLoggerTest : SysuiTestCase() { @Rule @JvmField val expect = Expect.create() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt index 072a497f1a65..f10a52a73586 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMeterTest.kt @@ -24,8 +24,8 @@ import android.content.Intent import android.graphics.Bitmap import android.graphics.drawable.Icon import android.stats.sysui.NotificationEnums -import android.testing.AndroidTestingRunner import android.widget.RemoteViews +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.NotificationUtils @@ -36,7 +36,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationMemoryMeterTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt index 4bb28ae46211..d7dde96baf32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalkerTest.kt @@ -3,9 +3,9 @@ package com.android.systemui.statusbar.notification.logging import android.app.Notification import android.graphics.Bitmap import android.graphics.drawable.Icon -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.widget.RemoteViews +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder @@ -17,7 +17,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotificationMemoryViewWalkerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt index 9b9cb8213c91..9f98fd4c8508 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.statusbar.notification.row import android.annotation.ColorInt import android.graphics.Color -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.Utils import com.android.systemui.res.R @@ -34,7 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class ActivatableNotificationViewTest : SysuiTestCase() { private val mContentView: View = mock() @@ -98,4 +98,4 @@ class ActivatableNotificationViewTest : SysuiTestCase() { assertThat(mView.topRoundness).isEqualTo(1f) assertThat(mView.roundableState.hashCode()).isEqualTo(roundableState.hashCode()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt index 0eae5fc209b4..fda5cd286074 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/BigPictureIconManagerTest.kt @@ -20,8 +20,8 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.net.Uri -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.NotificationDrawableConsumer import com.android.systemui.SysuiTestCase @@ -50,7 +50,7 @@ private const val MAX_IMAGE_SIZE = 512 // size of the test drawables in pixels @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class BigPictureIconManagerTest : SysuiTestCase() { private val testDispatcher = StandardTestDispatcher() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt index 7dcbd8084594..c5b19ab5862c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt @@ -25,8 +25,8 @@ import android.content.pm.ParceledListSlice import android.graphics.Color import android.graphics.drawable.ColorDrawable import android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View @@ -47,7 +47,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ChannelEditorDialogControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index 210b1a7f22f4..e738b616d227 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -21,8 +21,8 @@ import android.app.Notification import android.net.Uri import android.os.UserHandle import android.os.UserHandle.USER_ALL -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.statusbar.IStatusBarService @@ -71,7 +71,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ExpandableNotificationRowControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java index 9d2f32d77028..1c5f37cc60c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java @@ -31,10 +31,10 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -48,7 +48,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index aa79c23bdf74..7304bd62293d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -46,13 +46,13 @@ import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.DisplayMetrics; import android.view.View; import android.widget.ImageView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -87,7 +87,7 @@ import java.util.List; import java.util.function.Consumer; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class ExpandableNotificationRowTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java index ffb8646942b5..d04d6fc5f593 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java @@ -45,13 +45,13 @@ import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.UiThreadTest; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -72,7 +72,7 @@ import org.mockito.MockitoAnnotations; import java.util.Locale; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @UiThreadTest public class FeedbackInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; 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..c325791b1f05 --- /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 androidx.test.ext.junit.runners.AndroidJUnit4 +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(AndroidJUnit4::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/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java index 25172deb67a7..18fd42da78ea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java @@ -22,11 +22,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.annotation.NonNull; import androidx.core.os.CancellationSignal; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -48,7 +48,7 @@ import java.util.ArrayList; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotifBindPipelineTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt index e38adeb0fcd9..29252b27f6d0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.notification.row -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -33,7 +33,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotifInflationErrorManagerTest : SysuiTestCase() { private lateinit var manager: NotifInflationErrorManager diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java index 20cc01accbc3..8b1c95babe38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java @@ -26,9 +26,9 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.widget.RemoteViews; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -45,7 +45,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotifRemoteViewCacheImplTest extends SysuiTestCase { private NotifRemoteViewCacheImpl mNotifRemoteViewCache; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 03a84036b4b5..a355cd16ee15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -40,7 +40,6 @@ import android.os.AsyncTask; import android.os.CancellationSignal; import android.os.Handler; import android.os.Looper; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.TypedValue; @@ -49,6 +48,7 @@ import android.view.ViewGroup; import android.widget.RemoteViews; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import androidx.test.filters.Suppress; @@ -79,7 +79,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @Suppress public class NotificationContentInflaterTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 7332bc34b030..2bb610ac4449 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -20,7 +20,6 @@ import android.annotation.DimenRes import android.content.res.Resources import android.os.UserHandle import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.ViewUtils import android.view.NotificationHeaderView @@ -29,6 +28,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.internal.widget.NotificationActionListLayout @@ -60,7 +60,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotificationContentViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java index 97cb11e2f107..be89ab8c5cf8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java @@ -65,13 +65,13 @@ import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -103,7 +103,7 @@ import java.util.Optional; import java.util.concurrent.CountDownLatch; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationConversationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java index 907649b90956..625963f5ec7a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java @@ -57,12 +57,12 @@ import android.os.Handler; import android.os.UserManager; import android.provider.Settings; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.ArraySet; import android.view.View; import android.view.accessibility.AccessibilityManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -115,7 +115,7 @@ import java.util.Optional; * Tests for {@link NotificationGutsManager}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationGutsManagerTest extends SysuiTestCase { private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt index 1b85dfa5a087..0b5f8d5e948c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt @@ -31,11 +31,11 @@ import android.os.fakeExecutorHandler import android.os.userManager import android.provider.Settings import android.service.notification.NotificationListenerService.Ranking -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.util.ArraySet import android.view.View import android.view.accessibility.accessibilityManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.logging.MetricsLogger @@ -91,7 +91,7 @@ import org.mockito.invocation.InvocationOnMock /** Tests for [NotificationGutsManager] with the scene container enabled. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @EnableSceneContainer class NotificationGutsManagerWithScenesTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt index 7f9471e5271f..350f90dcbe91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.notification.row -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils import android.view.LayoutInflater import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -34,7 +34,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationGutsTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java index 13ced928175e..245a6a0b130c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInfoTest.java @@ -55,13 +55,13 @@ import android.os.RemoteException; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.telecom.TelecomManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -85,7 +85,7 @@ import org.mockito.junit.MockitoRule; import java.util.concurrent.CountDownLatch; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java index e9290289f7f2..027e899e20df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java @@ -29,13 +29,13 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.ViewUtils; import android.view.View; import android.view.ViewGroup; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; @@ -49,7 +49,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class NotificationMenuRowTest extends LeakCheckedTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt index 8261c1c4b42e..352b79f50b90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt @@ -22,8 +22,8 @@ import android.database.ContentObserver import android.net.Uri import android.os.Handler import android.provider.Settings.Secure -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -54,7 +54,7 @@ import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class NotificationSettingsControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java index 4a91cd239d87..22f1e4604bbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java @@ -23,11 +23,11 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.Mockito.mock; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableResources; import android.testing.UiThreadTest; import android.util.KeyValueListParser; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -41,7 +41,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @UiThreadTest public class NotificationSnoozeTest extends SysuiTestCase { private static final int RES_DEFAULT = 2; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java index 51665d987888..57b0f3f8d8c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/PartialConversationInfoTest.java @@ -41,7 +41,6 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.SpannableString; import android.view.LayoutInflater; @@ -49,6 +48,7 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -69,7 +69,7 @@ import org.mockito.junit.MockitoRule; import java.util.concurrent.CountDownLatch; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class PartialConversationInfoTest extends SysuiTestCase { private static final String TEST_PACKAGE_NAME = "test_package"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java index 1534c84fd99a..841cb4a3669b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java @@ -34,10 +34,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Log; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class RowContentBindStageTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt index 1c959af6ec3f..53a11989cca0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineConversationViewBinderTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.notification.row import android.app.Notification import android.app.Person import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -34,7 +34,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SingleLineConversationViewBinderTest : SysuiTestCase() { private lateinit var notificationBuilder: Notification.Builder diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt index f0fc349777b2..ee819c4df0ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewBinderTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.row import android.app.Notification import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -33,7 +33,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SingleLineViewBinderTest : SysuiTestCase() { private lateinit var notificationBuilder: Notification.Builder diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt index b67153a842ac..e025d3d36ab1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt @@ -27,9 +27,9 @@ import android.graphics.PorterDuffXfermode import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.core.graphics.drawable.toBitmap +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R @@ -48,7 +48,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @EnableFlags(AsyncHybridViewInflation.FLAG_NAME) class SingleLineViewInflaterTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt index d46763df8a75..f8533a532d32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/TextPrecomputerTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.statusbar.notification.row -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.text.PrecomputedText import android.text.TextPaint import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -29,7 +29,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class TextPrecomputerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt index c9602307216d..0dc871a523ee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ui/viewmodel/ActivatableNotificationViewModelTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.row.ui.viewmodel -import android.testing.AndroidTestingRunner +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 @@ -31,7 +31,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ActivatableNotificationViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java index a15b4cd37184..ec280a1d6d01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigPictureTemplateViewWrapperTest.java @@ -24,10 +24,10 @@ import android.app.Notification; import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.Icon; import android.os.Bundle; -import android.testing.AndroidTestingRunner; import android.view.LayoutInflater; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -40,7 +40,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class NotificationBigPictureTemplateViewWrapperTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt index fe2971c46c32..9d990b1d7edf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapperTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.graphics.drawable.AnimatedImageDrawable -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.internal.widget.CachingIconView @@ -38,7 +38,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationConversationTemplateViewWrapperTest : SysuiTestCase() { private lateinit var mRow: ExpandableNotificationRow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java index 2d72c7e0b714..f9a9704334a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java @@ -16,10 +16,10 @@ package com.android.systemui.statusbar.notification.row.wrapper; -import android.testing.AndroidTestingRunner; import android.view.View; import android.widget.RemoteViews; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -33,7 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationCustomViewWrapperTest extends SysuiTestCase { private ExpandableNotificationRow mRow; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt index f26c18b1d197..fc829d53a6b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMessagingTemplateViewWrapperTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.graphics.drawable.AnimatedImageDrawable -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.MessagingGroup import com.android.internal.widget.MessagingImageMessage @@ -36,7 +36,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationMessagingTemplateViewWrapperTest : SysuiTestCase() { private lateinit var mRow: ExpandableNotificationRow diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt index 54eed26adaf3..1ce3bada4609 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapperTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.row.wrapper import android.app.PendingIntent import android.app.PendingIntent.CancelListener import android.content.Intent -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils @@ -27,6 +26,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -46,7 +46,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationTemplateViewWrapperTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java index fad85f53a091..d17c8dbcf38d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java @@ -20,11 +20,11 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class NotificationViewWrapperTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt index 59d98c233f99..4c6e25a530a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.shelf.domain.interactor import android.os.PowerManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -41,7 +41,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.isNull import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class NotificationShelfInteractorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt index 917569ca787b..e2fb3ba11a02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.shelf.ui.viewmodel import android.os.PowerManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule @@ -44,7 +44,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class NotificationShelfViewModelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt index fb1594898f24..2349c252369c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -33,7 +33,7 @@ import org.junit.runner.RunWith private const val MAX_PULSE_HEIGHT = 100000f -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class AmbientStateTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt index f4e236e5bbf9..3a77d822eb7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.stack -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.LayoutInflater +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.res.R @@ -15,7 +15,7 @@ import org.junit.runner.RunWith * Tests for {@link MediaContainView}. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class MediaContainerViewTest : SysuiTestCase() { @@ -35,4 +35,4 @@ class MediaContainerViewTest : SysuiTestCase() { mediaContainerView.updateClipping() assertTrue(mediaContainerView.clipHeight == 10) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java index 3b16f1416935..14bbd38ece2c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java @@ -21,13 +21,13 @@ import static org.junit.Assert.assertNull; import android.app.Notification; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.NotificationHeaderView; import android.view.View; import android.widget.RemoteViews; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -45,7 +45,7 @@ import org.junit.runner.RunWith; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper //@DisableFlags(AsyncGroupHeaderViewInflation.FLAG_NAME) public class NotificationChildrenContainerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt index 745d20dd686b..48e8f88a15c0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt @@ -1,10 +1,11 @@ package com.android.systemui.statusbar.notification.stack +import android.platform.test.annotations.DisableFlags import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress import com.android.systemui.SysuiTestCase @@ -34,7 +35,7 @@ import org.mockito.Mockito.`when` as whenever /** Tests for {@link NotificationShelf}. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper open class NotificationShelfTest : SysuiTestCase() { @@ -69,8 +70,8 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME) fun testShadeWidth_BasedOnFractionToShade() { - mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME) setFractionToShade(0f) setOnLockscreen(true) @@ -85,8 +86,8 @@ open class NotificationShelfTest : SysuiTestCase() { } @Test + @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME) fun testShelfIsLong_WhenNotOnLockscreen() { - mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME) setFractionToShade(0f) setOnLockscreen(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index a66a1360f8f8..ce2491bb098e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac import static kotlinx.coroutines.flow.FlowKt.emptyFlow; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -41,21 +42,25 @@ import static org.mockito.Mockito.when; import android.metrics.LogMaker; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.nano.MetricsProto; +import com.android.systemui.ExpandHelper; import com.android.systemui.SysuiTestCase; import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.DisableSceneContainer; +import com.android.systemui.flags.EnableSceneContainer; import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -121,7 +126,7 @@ import javax.inject.Provider; */ @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); @@ -171,6 +176,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { @Mock private NotificationListViewBinder mViewBinder; @Mock private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController; + @Mock private ExpandHelper mExpandHelper; @Captor private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor; @@ -895,6 +901,50 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any()); } + @Test + @EnableSceneContainer + public void onTouchEvent_stopExpandingNotification_sceneContainerEnabled() { + boolean touchHandled = stopExpandingNotification(); + + verify(mNotificationStackScrollLayout).startOverscrollAfterExpanding(); + verify(mNotificationStackScrollLayout, never()).dispatchDownEventToScroller(any()); + assertTrue(touchHandled); + } + + @Test + @DisableSceneContainer + public void onTouchEvent_stopExpandingNotification_sceneContainerDisabled() { + stopExpandingNotification(); + + verify(mNotificationStackScrollLayout, never()).startOverscrollAfterExpanding(); + verify(mNotificationStackScrollLayout).dispatchDownEventToScroller(any()); + } + + private boolean stopExpandingNotification() { + when(mNotificationStackScrollLayout.getExpandHelper()).thenReturn(mExpandHelper); + when(mNotificationStackScrollLayout.getIsExpanded()).thenReturn(true); + when(mNotificationStackScrollLayout.getExpandedInThisMotion()).thenReturn(true); + when(mNotificationStackScrollLayout.isExpandingNotification()).thenReturn(true); + + when(mExpandHelper.onTouchEvent(any())).thenAnswer(i -> { + when(mNotificationStackScrollLayout.isExpandingNotification()).thenReturn(false); + return false; + }); + + initController(/* viewIsAttached= */ true); + NotificationStackScrollLayoutController.TouchHandler touchHandler = + mController.getTouchHandler(); + + return touchHandler.onTouchEvent(MotionEvent.obtain( + /* downTime= */ 0, + /* eventTime= */ 0, + MotionEvent.ACTION_DOWN, + 0, + 0, + /* metaState= */ 0 + )); + } + private LogMaker logMatcher(int category, int type) { return argThat(new LogMatcher(category, type)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index 939d0556e347..f461e2f67d20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -54,7 +54,6 @@ import android.graphics.Rect; import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; import android.util.MathUtils; @@ -65,6 +64,7 @@ import android.view.WindowInsets; import android.view.WindowInsetsAnimation; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; @@ -114,7 +114,7 @@ import java.util.function.Consumer; * Tests for {@link NotificationStackScrollLayout}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class NotificationStackScrollLayoutTest extends SysuiTestCase { @@ -207,6 +207,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { .thenReturn(mNotificationRoundnessManager); mStackScroller.setController(mStackScrollLayoutController); mStackScroller.setShelf(mNotificationShelf); + when(mStackScroller.getExpandHelper()).thenReturn(mExpandHelper); doNothing().when(mGroupExpansionManager).collapseGroups(); doNothing().when(mExpandHelper).cancelImmediately(); @@ -1139,6 +1140,14 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { assertFalse(mStackScroller.mHeadsUpAnimatingAway); } + @Test + @EnableSceneContainer + public void finishExpanding_sceneContainerEnabled() { + mStackScroller.startOverscrollAfterExpanding(); + verify(mStackScroller.getExpandHelper()).finishExpanding(); + assertTrue(mStackScroller.getIsBeingDragged()); + } + private MotionEvent captureTouchSentToSceneFramework() { ArgumentCaptor<MotionEvent> captor = ArgumentCaptor.forClass(MotionEvent.class); verify(mStackScrollLayoutController).sendTouchToSceneFramework(captor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index 6fec9ad7bd66..dae5542123ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.notification.stack import android.annotation.DimenRes import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.view.View.VISIBLE +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -44,7 +44,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class NotificationStackSizeCalculatorTest : SysuiTestCase() { @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java index 85a2bdd21073..2d119174efff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java @@ -37,12 +37,12 @@ import android.animation.Animator; import android.animation.ValueAnimator.AnimatorUpdateListener; import android.os.Handler; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -70,7 +70,7 @@ import java.util.stream.Collectors; * Tests for {@link NotificationSwipeHelper}. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper() public class NotificationSwipeHelperTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt index e30947ce84bd..660eb308fdf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.stack -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags @@ -15,7 +15,7 @@ import org.junit.runner.RunWith /** Tests for {@link NotificationTargetsHelper}. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationTargetsHelperTest : SysuiTestCase() { private val featureFlags = FakeFeatureFlags() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt index 82725d656bb9..a6fb7187ff5f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt @@ -394,8 +394,20 @@ class StackScrollAlgorithmTest : SysuiTestCase() { } @Test + fun resetViewStates_shadeCollapsed_emptyShadeViewBecomesTransparent() { + ambientState.expansionFraction = 0f + stackScrollAlgorithm.initView(context) + hostView.removeAllViews() + hostView.addView(emptyShadeView) + + stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) + + assertThat(emptyShadeView.viewState.alpha).isEqualTo(0f) + } + + @Test fun resetViewStates_isOnKeyguard_emptyShadeViewBecomesOpaque() { - ambientState.setStatusBarState(StatusBarState.SHADE) + ambientState.setStatusBarState(StatusBarState.KEYGUARD) ambientState.fractionToShade = 0.25f stackScrollAlgorithm.initView(context) hostView.removeAllViews() @@ -403,7 +415,8 @@ class StackScrollAlgorithmTest : SysuiTestCase() { stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0) - assertThat(emptyShadeView.viewState.alpha).isEqualTo(1f) + val expected = getContentAlpha(ambientState.fractionToShade) + assertThat(emptyShadeView.viewState.alpha).isEqualTo(expected) } @Test 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..798465e7b165 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 @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.notification.stack import android.platform.test.annotations.EnableFlags -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule @@ -48,7 +48,7 @@ private const val FULL_SHADE_APPEAR_TRANSLATION = 300 private const val HEADS_UP_ABOVE_SCREEN = 80 @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class StackStateAnimatorTest : SysuiTestCase() { @@ -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/notification/stack/ViewStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt index cd6bb5f4966a..e493420b64a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ViewStateTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.assertDoesNotLogWtf @@ -27,7 +27,7 @@ import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ViewStateTest : SysuiTestCase() { private val viewState = ViewState() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt index e2ac2038be32..e46906fb5192 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HideNotificationsInteractorTest.kt @@ -17,10 +17,10 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor import android.content.res.Configuration import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.view.Surface import android.view.Surface.ROTATION_0 import android.view.Surface.ROTATION_90 +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl @@ -52,7 +52,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) open class HideNotificationsInteractorTest : SysuiTestCase() { private val testScope = TestScope() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java index 84cd518cf85a..f0bc655a554d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java @@ -45,10 +45,10 @@ import android.hardware.display.ColorDisplayManager; import android.hardware.display.NightDisplayListener; import android.os.Handler; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -89,7 +89,7 @@ import java.util.List; import javax.inject.Named; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class AutoTileManagerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index f49dc9895da5..285949a41fcd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -37,11 +37,11 @@ import android.hardware.biometrics.BiometricSourceType; import android.os.Handler; import android.os.PowerManager; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; import android.view.ViewRootImpl; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -55,6 +55,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource; import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -76,7 +77,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class BiometricsUnlockControllerTest extends SysuiTestCase { @@ -337,7 +338,9 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { // WHEN we want to unlock collapse mBiometricUnlockController.startWakeAndUnlock( - BiometricUnlockController.MODE_UNLOCK_COLLAPSING); + BiometricUnlockController.MODE_UNLOCK_COLLAPSING, + BiometricUnlockSource.FINGERPRINT_SENSOR + ); verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated( /* strongAuth */ eq(false)); @@ -501,7 +504,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { TransitionState.STARTED ) ); - verify(mBiometricUnlockInteractor, never()).setBiometricUnlockState(anyInt()); + verify(mBiometricUnlockInteractor, never()).setBiometricUnlockState(anyInt(), any()); mBiometricUnlockController.consumeTransitionStepOnStartedKeyguardState( new TransitionStep( @@ -511,7 +514,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { TransitionState.STARTED ) ); - verify(mBiometricUnlockInteractor).setBiometricUnlockState(eq(MODE_NONE)); + verify(mBiometricUnlockInteractor).setBiometricUnlockState(eq(MODE_NONE), eq(null)); } @Test 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..5675915506a5 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 @@ -30,9 +30,9 @@ import android.content.ComponentName; import android.os.PowerManager; import android.os.UserHandle; import android.os.Vibrator; -import android.testing.AndroidTestingRunner; import android.view.HapticFeedbackConstants; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -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; @@ -70,7 +71,7 @@ import org.mockito.stubbing.Answer; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Mock private CentralSurfaces mCentralSurfaces; @@ -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/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index f666d8e3ffa7..cde241bbe918 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -21,9 +21,13 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; import static android.provider.Settings.Global.HEADS_UP_ON; +import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR; +import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE; import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; +import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; import static com.android.systemui.statusbar.StatusBarState.SHADE; +import static com.android.systemui.statusbar.phone.CentralSurfaces.MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU; import static com.google.common.truth.Truth.assertThat; @@ -42,6 +46,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import static java.util.Collections.emptySet; @@ -52,6 +57,9 @@ import android.app.WallpaperManager; import android.app.trust.TrustManager; import android.content.BroadcastReceiver; import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Rect; import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; @@ -64,16 +72,20 @@ import android.os.IThermalService; import android.os.Looper; import android.os.PowerManager; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.service.dreams.IDreamManager; import android.support.test.metricshelper.MetricsAsserts; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; +import android.view.WindowManager; +import android.view.WindowMetrics; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.compose.animation.scene.ObservableTransitionState; @@ -97,8 +109,6 @@ import com.android.systemui.charging.WiredChargingRippleController; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.colorextraction.SysuiColorExtractor; -import com.android.systemui.communal.data.repository.CommunalRepository; -import com.android.systemui.communal.domain.interactor.CommunalInteractor; import com.android.systemui.communal.shared.model.CommunalScenes; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; @@ -137,6 +147,8 @@ import com.android.systemui.shade.ShadeControllerImpl; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeLogger; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.KeyboardShortcutListSearch; +import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.LightRevealScrim; import com.android.systemui.statusbar.LockscreenShadeTransitionController; @@ -202,6 +214,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.io.ByteArrayOutputStream; @@ -211,8 +224,9 @@ import java.util.Optional; import javax.inject.Provider; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) +@EnableFlags(FLAG_LIGHT_REVEAL_MIGRATION) public class CentralSurfacesImplTest extends SysuiTestCase { private static final int FOLD_STATE_FOLDED = 0; @@ -227,8 +241,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private final TestScope mTestScope = mKosmos.getTestScope(); - private final CommunalInteractor mCommunalInteractor = mKosmos.getCommunalInteractor(); - private final CommunalRepository mCommunalRepository = mKosmos.getCommunalRepository(); @Mock private NotificationsController mNotificationsController; @Mock private LightBarController mLightBarController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -326,18 +338,22 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock IPowerManager mPowerManagerService; @Mock ActivityStarter mActivityStarter; @Mock private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor; + @Mock private KeyboardShortcuts mKeyboardShortcuts; + @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch; + @Mock private PackageManager mPackageManager; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings(); private final SystemSettings mSystemSettings = new FakeSettings(); private final FakeEventLog mFakeEventLog = new FakeEventLog(); - private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); + private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); private final FakeExecutor mUiBgExecutor = new FakeExecutor(mFakeSystemClock); private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); private final InitController mInitController = new InitController(); private final DumpManager mDumpManager = new DumpManager(); private final ScreenLifecycle mScreenLifecycle = new ScreenLifecycle(mDumpManager); + private MessageRouterImpl mMessageRouter = new MessageRouterImpl(mMainExecutor); private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor = mKosmos.getBrightnessMirrorShowingInteractor(); @@ -347,14 +363,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); // Set default value to avoid IllegalStateException. - mFeatureFlags.set(Flags.SHORTCUT_LIST_SEARCH_LAYOUT, false); - mSetFlagsRule.enableFlags(FLAG_LIGHT_REVEAL_MIGRATION); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); // Turn AOD on and toggle feature flag for jank fixes mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true); when(mDozeParameters.getAlwaysOn()).thenReturn(true); - if (!SceneContainerFlag.isEnabled()) { - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); - } IThermalService thermalService = mock(IThermalService.class); mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService, @@ -382,7 +394,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mock(UiEventLogger.class), mUserTracker, mAvalancheProvider, - mSystemSettings); + mSystemSettings, + mPackageManager); mVisualInterruptionDecisionProvider.start(); mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class)); @@ -462,6 +475,16 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } private void createCentralSurfaces() { + mMainExecutor = new FakeExecutor(mFakeSystemClock); + mMessageRouter = new MessageRouterImpl(mMainExecutor); + mKeyboardShortcuts = mock(KeyboardShortcuts.class); + mKeyboardShortcutListSearch = mock(KeyboardShortcutListSearch.class); + // Test setup for legacy version + mKeyboardShortcuts.mContext = mContext; + mKeyboardShortcutListSearch.mContext = mContext; + KeyboardShortcuts.sInstance = mKeyboardShortcuts; + KeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch; + ConfigurationController configurationController = new ConfigurationControllerImpl(mContext); mCentralSurfaces = new CentralSurfacesImpl( mContext, @@ -504,7 +527,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mScreenLifecycle, mWakefulnessLifecycle, mPowerInteractor, - mCommunalInteractor, + mKosmos.getCommunalInteractor(), mStatusBarStateController, Optional.of(mBubbles), () -> mNoteTaskController, @@ -554,7 +577,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mFeatureFlags, mKeyguardUnlockAnimationController, mMainExecutor, - new MessageRouterImpl(mMainExecutor), + mMessageRouter, mWallpaperManager, Optional.of(mStartingSurface), mActivityTransitionAnimator, @@ -813,6 +836,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); @@ -824,7 +848,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - public void testOccludingQSNotExpanded_transitionToAuthScrimmed() { + @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); // GIVEN device occluded and panel is NOT expanded @@ -838,6 +863,39 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() { + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // GIVEN device occluded and panel is NOT expanded + mCentralSurfaces.setBarStateForTest(SHADE); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false); + + mCentralSurfaces.updateScrimController(); + + // Tests the safeguard to reset the scrimstate + verify(mScrimController, never()).transitionTo(any()); + } + + @Test + @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) + public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() { + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // GIVEN device occluded and panel is NOT expanded + mCentralSurfaces.setBarStateForTest(SHADE); + when(mKeyguardStateController.isOccluded()).thenReturn(false); + when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); + + mCentralSurfaces.updateScrimController(); + + // Tests the safeguard to reset the scrimstate + verify(mScrimController, never()).transitionTo(eq(ScrimState.KEYGUARD)); + } + + @Test + @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testOccludingQSExpanded_transitionToAuthScrimmedShade() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); @@ -854,16 +912,18 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Test public void testEnteringGlanceableHub_updatesScrim() { // Transition to the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( - CommunalScenes.Communal))); + mKosmos.getCommunalRepository() + .setTransitionState( + flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal))); mTestScope.getTestScheduler().runCurrent(); // ScrimState also transitions. verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB); // Transition away from the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( - CommunalScenes.Blank))); + mKosmos.getCommunalRepository() + .setTransitionState( + flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank))); mTestScope.getTestScheduler().runCurrent(); // ScrimState goes back to UNLOCKED. @@ -877,16 +937,18 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isDreaming()).thenReturn(true); // Transition to the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( - CommunalScenes.Communal))); + mKosmos.getCommunalRepository() + .setTransitionState( + flowOf(new ObservableTransitionState.Idle(CommunalScenes.Communal))); mTestScope.getTestScheduler().runCurrent(); // ScrimState also transitions. verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); // Transition away from the glanceable hub. - mCommunalRepository.setTransitionState(flowOf(new ObservableTransitionState.Idle( - CommunalScenes.Blank))); + mKosmos.getCommunalRepository() + .setTransitionState( + flowOf(new ObservableTransitionState.Idle(CommunalScenes.Blank))); mTestScope.getTestScheduler().runCurrent(); // ScrimState goes back to UNLOCKED. @@ -1081,18 +1143,16 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + @EnableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX) public void updateResources_flagEnabled_doesNotUpdateStatusBarWindowHeight() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX); - mCentralSurfaces.updateResources(); verify(mStatusBarWindowController, never()).refreshStatusBarHeight(); } @Test + @DisableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX) public void updateResources_flagDisabled_updatesStatusBarWindowHeight() { - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_TRUNCATED_STATUS_BAR_ICONS_FIX); - mCentralSurfaces.updateResources(); verify(mStatusBarWindowController).refreshStatusBarHeight(); @@ -1126,6 +1186,194 @@ public class CentralSurfacesImplTest extends SysuiTestCase { verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any()); } + @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void dismissKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotDismissAny() { + switchToLargeScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); + createCentralSurfaces(); + + dismissKeyboardShortcuts(); + + verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void dismissKeyboardShortcuts_largeScreen_newFlagsDisabled_dismissesTabletVersion() { + switchToLargeScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); + createCentralSurfaces(); + + dismissKeyboardShortcuts(); + + verify(mKeyboardShortcutListSearch).dismissKeyboardShortcuts(); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void dismissKeyboardShortcuts_largeScreen_bothFlagsDisabled_dismissesPhoneVersion() { + switchToLargeScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); + createCentralSurfaces(); + + dismissKeyboardShortcuts(); + + verify(mKeyboardShortcuts).dismissKeyboardShortcuts(); + verifyNoMoreInteractions(mKeyboardShortcutListSearch); + } + + @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void dismissKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotDismissAny() { + switchToSmallScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); + createCentralSurfaces(); + + dismissKeyboardShortcuts(); + + verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void dismissKeyboardShortcuts_smallScreen_newFlagsDisabled_dismissesPhoneVersion() { + switchToSmallScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); + createCentralSurfaces(); + + dismissKeyboardShortcuts(); + + verify(mKeyboardShortcuts).dismissKeyboardShortcuts(); + verifyNoMoreInteractions(mKeyboardShortcutListSearch); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void dismissKeyboardShortcuts_smallScreen_bothFlagsDisabled_dismissesPhoneVersion() { + switchToSmallScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); + createCentralSurfaces(); + + dismissKeyboardShortcuts(); + + verify(mKeyboardShortcuts).dismissKeyboardShortcuts(); + verifyNoMoreInteractions(mKeyboardShortcutListSearch); + } + + @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void toggleKeyboardShortcuts_largeScreen_bothFlagsEnabled_doesNotTogglesAny() { + switchToLargeScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); + createCentralSurfaces(); + + int deviceId = 321; + toggleKeyboardShortcuts(/* deviceId= */ deviceId); + + verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void toggleKeyboardShortcuts_largeScreen_newFlagsDisabled_togglesTabletVersion() { + switchToLargeScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); + createCentralSurfaces(); + + int deviceId = 654; + toggleKeyboardShortcuts(deviceId); + + verify(mKeyboardShortcutListSearch).showKeyboardShortcuts(deviceId); + verifyNoMoreInteractions(mKeyboardShortcuts); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void toggleKeyboardShortcuts_largeScreen_bothFlagsDisabled_togglesPhoneVersion() { + switchToLargeScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); + createCentralSurfaces(); + + int deviceId = 987; + toggleKeyboardShortcuts(deviceId); + + verify(mKeyboardShortcuts).showKeyboardShortcuts(deviceId); + verifyNoMoreInteractions(mKeyboardShortcutListSearch); + } + + @Test + @EnableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void toggleKeyboardShortcuts_smallScreen_bothFlagsEnabled_doesNotToggleAny() { + switchToSmallScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); + createCentralSurfaces(); + + int deviceId = 789; + toggleKeyboardShortcuts(/* deviceId= */ deviceId); + + verifyNoMoreInteractions(mKeyboardShortcuts, mKeyboardShortcutListSearch); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void toggleKeyboardShortcuts_smallScreen_newFlagsDisabled_togglesPhoneVersion() { + switchToSmallScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, true); + createCentralSurfaces(); + + int deviceId = 456; + toggleKeyboardShortcuts(deviceId); + + verify(mKeyboardShortcuts).showKeyboardShortcuts(deviceId); + verifyNoMoreInteractions(mKeyboardShortcutListSearch); + } + + @Test + @DisableFlags(FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE) + public void toggleKeyboardShortcuts_smallScreen_bothFlagsDisabled_togglesPhoneVersion() { + switchToSmallScreen(); + mFeatureFlags.set(SHORTCUT_LIST_SEARCH_LAYOUT, false); + createCentralSurfaces(); + + int deviceId = 123; + toggleKeyboardShortcuts(deviceId); + + verify(mKeyboardShortcuts).showKeyboardShortcuts(deviceId); + verifyNoMoreInteractions(mKeyboardShortcutListSearch); + } + + private void dismissKeyboardShortcuts() { + mMessageRouter.sendMessage(MSG_DISMISS_KEYBOARD_SHORTCUTS_MENU); + mMainExecutor.runAllReady(); + } + + private void toggleKeyboardShortcuts(int deviceId) { + mMessageRouter.sendMessage(new CentralSurfaces.KeyboardShortcutsMessage(deviceId)); + mMainExecutor.runAllReady(); + } + + private void switchToLargeScreen() { + switchToScreenSize(1280, 800); + } + + private void switchToSmallScreen() { + switchToScreenSize(504, 1122); + } + + private void switchToScreenSize(int widthDp, int heightDp) { + WindowMetrics windowMetrics = Mockito.mock(WindowMetrics.class); + WindowManager windowManager = Mockito.mock(WindowManager.class); + + Configuration configuration = new Configuration(); + configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT; + mContext.getOrCreateTestableResources().overrideConfiguration(configuration); + + when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, widthDp, heightDp)); + when(windowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics); + mContext.addMockSystemService(WindowManager.class, windowManager); + } + /** * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard} * to reconfigure the keyguard to reflect the requested showing/occluded states. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt index 56d23978a5c7..942ea65ec49e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt @@ -21,7 +21,7 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_TYPE_CAR import android.os.LocaleList -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener @@ -36,7 +36,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import java.util.Locale -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ConfigurationControllerImplTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java index 34c43ef52a00..3b3ec263145a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeScrimControllerTest.java @@ -20,9 +20,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -36,7 +36,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class DozeScrimControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt index 5d42d5167c27..a3e2d1949a29 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.phone import android.hardware.devicestate.DeviceState -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R import com.android.systemui.SysuiTestCase @@ -30,7 +30,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations.initMocks -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class FoldStateListenerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index 3e9006e5268c..0d06b6431e1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -26,12 +26,12 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.View; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -62,7 +62,7 @@ import org.junit.runner.RunWith; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class HeadsUpAppearanceControllerTest extends SysuiTestCase { 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/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index fd295b57379c..cf87afbbff34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -26,8 +26,10 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import android.content.res.Resources; -import android.testing.AndroidTestingRunner; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.Flags; @@ -48,7 +50,7 @@ import org.mockito.MockitoSession; import org.mockito.quality.Strictness; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { private static final int SCREEN_HEIGHT = 2000; private static final int EMPTY_HEIGHT = 0; @@ -297,8 +299,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { } @Test + @DisableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOff_usesResource() { - mSetFlagsRule.disableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX); int keyguardSplitShadeTopMargin = 100; int largeScreenHeaderHeightResource = 70; when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)) @@ -316,8 +318,8 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { } @Test + @EnableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX) public void notifPaddingMakesUpToFullMarginInSplitShade_refactorFlagOn_usesHelper() { - mSetFlagsRule.enableFlags(Flags.FLAG_CENTRALIZED_STATUS_BAR_HEIGHT_FIX); int keyguardSplitShadeTopMargin = 100; int largeScreenHeaderHeightHelper = 50; int largeScreenHeaderHeightResource = 70; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java index b0aa2d3934cc..d880becaa4bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardDismissUtilTest.java @@ -20,8 +20,8 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class KeyguardDismissUtilTest extends SysuiTestCase { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java index 5cea931e2070..109cd948b5b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextViewTest.java @@ -21,10 +21,10 @@ import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE; import static com.google.common.truth.Truth.assertThat; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardIndicationTextViewTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index 8e9840acc414..f3d640758cf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -42,11 +42,11 @@ import android.os.UserManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.CarrierTextController; @@ -71,7 +71,6 @@ import com.android.systemui.res.R; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.shade.data.repository.FakeShadeRepository; import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository; import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor; @@ -100,7 +99,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock @@ -144,8 +143,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock private SecureSettings mSecureSettings; @Mock private CommandQueue mCommandQueue; @Mock private KeyguardLogger mLogger; - - @Mock private NotificationMediaManager mNotificationMediaManager; @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory; private TestShadeViewStateProvider mShadeViewStateProvider; @@ -178,11 +175,13 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { keyguardTransitionInteractor, () -> mKosmos.getSceneInteractor(), () -> mKosmos.getFromGoneTransitionInteractor(), + () -> mKosmos.getFromLockscreenTransitionInteractor(), () -> mKosmos.getSharedNotificationContainerInteractor(), mTestScope); mViewModel = new KeyguardStatusBarViewModel( mTestScope.getBackgroundScope(), + mKosmos.getHeadsUpNotificationInteractor(), mKeyguardInteractor, new KeyguardStatusBarInteractor(new FakeKeyguardStatusBarRepository()), mBatteryController); @@ -224,7 +223,6 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mFakeExecutor, mBackgroundExecutor, mLogger, - mNotificationMediaManager, mStatusOverlayHoverListenerFactory ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java index c44f979fa971..0932a0c9307c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewTest.java @@ -18,11 +18,11 @@ package com.android.systemui.statusbar.phone; import static com.google.common.truth.Truth.assertThat; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -33,7 +33,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class KeyguardStatusBarViewTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java index f91064b49e95..782ca91bc8fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyLightsOutNotifControllerTest.java @@ -26,7 +26,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; import android.view.View; @@ -35,6 +34,7 @@ import android.view.WindowInsets; import android.view.WindowManager; import androidx.lifecycle.Observer; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -54,7 +54,7 @@ import org.mockito.MockitoAnnotations; import java.util.Objects; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) public class LegacyLightsOutNotifControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index 9d53b9c66b33..fea0e72fe577 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -21,9 +21,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.platform.test.annotations.DisableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.Flags; @@ -49,7 +49,7 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME) public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt index e7b287c5bdc8..518b327036cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.phone import android.graphics.Color import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.view.WindowInsetsController import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.statusbar.LetterboxDetails import com.android.internal.view.AppearanceRegion @@ -36,7 +36,7 @@ import org.mockito.Mock import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LetterboxAppearanceCalculatorTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt index 1cc0bd3cb36c..788c2cb2a485 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt @@ -21,8 +21,8 @@ import android.app.WallpaperManager.OnColorsChangedListener import android.graphics.Color import android.os.Handler import android.os.Looper -import android.testing.AndroidTestingRunner import android.view.IWindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor @@ -40,7 +40,7 @@ import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LetterboxBackgroundProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java index 7271a5efc377..a27073c77eb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java @@ -35,10 +35,10 @@ import static org.mockito.Mockito.when; import android.graphics.Color; import android.graphics.Rect; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.annotation.ColorInt; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor.GradientColors; @@ -67,7 +67,7 @@ import java.util.Arrays; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class LightBarControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java index f71114d92aa3..43c19b833646 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java @@ -28,9 +28,9 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.policy.GestureNavigationSettingsObserver; @@ -48,7 +48,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class LightBarTransitionsControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt index 9f4e1dd8cc4d..9d97e5a5686e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.phone import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.StatusBarIconView @@ -34,7 +34,7 @@ import org.mockito.Mockito.`when` as whenever /** Tests for {@link NotificationIconContainer}. */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class NotificationIconContainerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java index ccd1a8c7a9b2..9522e1f65a4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationTapHelperTest.java @@ -22,11 +22,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Resources; -import android.testing.AndroidTestingRunner; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -42,7 +42,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class NotificationTapHelperTest extends SysuiTestCase { private NotificationTapHelper mNotificationTapHelper; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt index 8d2c1588fb62..f2f336c45d7f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicyTest.kt @@ -22,9 +22,9 @@ import android.app.admin.DevicePolicyResourcesManager import android.content.SharedPreferences import android.os.UserManager import android.telecom.TelecomManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -79,7 +79,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @OptIn(ExperimentalCoroutinesApi::class) @SmallTest diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 1000329cb276..416a869bf2f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -49,12 +49,12 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Color; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.ViewUtils; import android.util.MathUtils; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.colorextraction.ColorExtractor.GradientColors; @@ -107,7 +107,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ScrimControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt index 61da701ce971..b9cfe21dcad3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt @@ -17,10 +17,10 @@ package com.android.systemui.statusbar.phone import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener @@ -37,7 +37,7 @@ import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class StatusBarBoundsProviderTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 6b3c0053e738..3ca4c594cede 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -41,7 +41,6 @@ import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.service.trust.TrustAgentService; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; import android.view.View; @@ -55,6 +54,7 @@ import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.LatencyTracker; @@ -119,7 +119,7 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 127a3d7b208b..9fa392f3a337 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -51,9 +51,9 @@ import android.os.RemoteException; import android.os.UserHandle; import android.service.dreams.IDreamManager; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; @@ -119,7 +119,7 @@ import java.util.List; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { @@ -168,6 +168,8 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { private FakePowerRepository mPowerRepository; @Mock private UserTracker mUserTracker; + @Mock + private HeadsUpManager mHeadsUpManager; private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock()); private ExpandableNotificationRow mNotificationRow; private ExpandableNotificationRow mBubbleNotificationRow; @@ -222,13 +224,12 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mScreenOffAnimationController, mStatusBarStateController).getPowerInteractor(); - HeadsUpManager headsUpManager = mock(HeadsUpManager.class); NotificationLaunchAnimatorControllerProvider notificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider( new NotificationLaunchAnimationInteractor( new NotificationLaunchAnimationRepository()), mock(NotificationListContainer.class), - headsUpManager, + mHeadsUpManager, mJankMonitor); mNotificationActivityStarter = new StatusBarNotificationActivityStarter( @@ -237,7 +238,7 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { mHandler, mUiBgExecutor, mVisibilityProvider, - headsUpManager, + mHeadsUpManager, mActivityStarter, mCommandQueue, mClickNotifier, @@ -417,6 +418,51 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { } @Test + public void testOnNotificationBubbleIconClicked_unbubble_keyGuardShowing() + throws RemoteException { + NotificationEntry entry = mBubbleNotificationRow.getEntry(); + StatusBarNotification sbn = entry.getSbn(); + + // Given + sbn.getNotification().contentIntent = mContentIntent; + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(true); + + // When + mNotificationActivityStarter.onNotificationBubbleIconClicked(entry); + + // Then + verify(mBubblesManager).onUserChangedBubble(entry, false); + + verify(mHeadsUpManager).removeNotification(entry.getKey(), true); + + verifyNoMoreInteractions(mContentIntent); + verifyNoMoreInteractions(mShadeController); + } + + @Test + public void testOnNotificationBubbleIconClicked_bubble_keyGuardShowing() { + NotificationEntry entry = mNotificationRow.getEntry(); + StatusBarNotification sbn = entry.getSbn(); + + // Given + sbn.getNotification().contentIntent = mContentIntent; + when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(true); + + // When + mNotificationActivityStarter.onNotificationBubbleIconClicked(entry); + + // Then + verify(mBubblesManager).onUserChangedBubble(entry, true); + + verify(mHeadsUpManager).removeNotification(entry.getKey(), true); + + verify(mContentIntent, atLeastOnce()).isActivity(); + verifyNoMoreInteractions(mContentIntent); + } + + @Test public void testOnFullScreenIntentWhenDozing_wakeUpDevice() { // GIVEN entry that can has a full screen intent that can show PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 1, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index a8c5fc357c7c..95472cad4b90 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -34,10 +34,10 @@ import android.app.PendingIntent; import android.app.StatusBarManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.InitController; @@ -85,7 +85,7 @@ import java.util.List; import java.util.Set; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper() public class StatusBarNotificationPresenterTest extends SysuiTestCase { private StatusBarNotificationPresenter mStatusBarNotificationPresenter; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java index 929099a8f1f7..35888a5fa734 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java @@ -24,10 +24,10 @@ import static org.mockito.Mockito.when; import static org.mockito.internal.verification.VerificationModeFactory.times; import android.content.Intent; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { @Mock private DeviceProvisionedController mDeviceProvisionedController; @@ -103,4 +103,4 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).showBouncer(true); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt index 1455693fc54b..11dd587a04ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusOverlayHoverListenerTest.kt @@ -21,7 +21,6 @@ import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.PaintDrawable import android.os.SystemClock -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils @@ -30,6 +29,7 @@ import android.view.View import android.view.ViewGroupOverlay import android.widget.LinearLayout import androidx.annotation.ColorInt +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -45,7 +45,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class StatusOverlayHoverListenerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt index dedd0afb6127..b560c591af1e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIBottomSheetDialogTest.kt @@ -15,9 +15,9 @@ package com.android.systemui.statusbar.phone import android.app.Dialog import android.content.res.Configuration -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.WindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.testScope @@ -39,7 +39,7 @@ import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class SystemUIBottomSheetDialogTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index c8ff20b31aae..624c070e95e0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.phone import android.os.Handler import android.os.PowerManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.Display +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase @@ -51,7 +51,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 66211c922abb..fdf77ae87011 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -426,7 +426,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.GONE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); } @Test @@ -438,7 +438,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.VISIBLE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility()); } @@ -452,7 +452,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); assertEquals(View.GONE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); } @Test @@ -465,7 +465,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.GONE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); } @Test @@ -477,21 +477,21 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.VISIBLE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); // Ongoing call ended when(mOngoingCallController.hasOngoingCall()).thenReturn(false); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.GONE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); // Ongoing call started when(mOngoingCallController.hasOngoingCall()).thenReturn(true); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); assertEquals(View.VISIBLE, - mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); + mFragment.getView().findViewById(R.id.ongoing_activity_chip).getVisibility()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt index 05464f3b715a..4d6798be9211 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt @@ -106,7 +106,7 @@ class OngoingCallControllerTest : SysuiTestCase() { fun setUp() { allowTestableLooperAsMainThread() TestableLooper.get(this).runWithLooper { - chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null) + chipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_activity_chip, null) } MockitoAnnotations.initMocks(this) @@ -206,7 +206,7 @@ class OngoingCallControllerTest : SysuiTestCase() { View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth) + assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) .isEqualTo(0) } @@ -222,7 +222,7 @@ class OngoingCallControllerTest : SysuiTestCase() { View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth) + assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) .isGreaterThan(0) } @@ -237,7 +237,7 @@ class OngoingCallControllerTest : SysuiTestCase() { View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) ) - assertThat(chipView.findViewById<View>(R.id.ongoing_call_chip_time)?.measuredWidth) + assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth) .isGreaterThan(0) } @@ -472,7 +472,10 @@ class OngoingCallControllerTest : SysuiTestCase() { lateinit var newChipView: View TestableLooper.get(this).runWithLooper { - newChipView = LayoutInflater.from(mContext).inflate(R.layout.ongoing_call_chip, null) + newChipView = LayoutInflater.from(mContext).inflate( + R.layout.ongoing_activity_chip, + null + ) } // Change the chip view associated with the controller. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt index 598b12ccdc38..eb2538ec032e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt @@ -20,6 +20,7 @@ import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.demomode.DemoMode @@ -60,7 +61,6 @@ import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -73,7 +73,7 @@ import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class MobileRepositorySwitcherTest : SysuiTestCase() { private lateinit var underTest: MobileRepositorySwitcher private lateinit var realRepo: MobileConnectionsRepositoryImpl diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt index 265440154d77..237aabccfbd9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod import android.telephony.TelephonyManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -44,7 +44,7 @@ import org.mockito.MockitoAnnotations @SmallTest @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class CarrierMergedConnectionRepositoryTest : SysuiTestCase() { private lateinit var underTest: CarrierMergedConnectionRepository 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..96e599f8f4d0 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 @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod +import android.annotation.SuppressLint import android.content.Intent import android.net.ConnectivityManager import android.net.Network @@ -27,8 +28,10 @@ import android.net.NetworkCapabilities.TRANSPORT_WIFI import android.net.vcn.VcnTransportInfo import android.net.wifi.WifiInfo import android.net.wifi.WifiManager +import android.os.Bundle import android.os.ParcelUuid import android.telephony.CarrierConfigManager +import android.telephony.ServiceState import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID @@ -53,6 +56,7 @@ import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository @@ -295,6 +299,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) @@ -551,6 +599,51 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { assertThat(mobileRepo.getIsCarrierMerged()).isFalse() } + @SuppressLint("UnspecifiedRegisterReceiverFlag") + @Test + fun testDeviceServiceStateFromBroadcast_eagerlyWatchesBroadcast() = + testScope.runTest { + // Value starts out empty (null) + assertThat(underTest.deviceServiceState.value).isNull() + + // WHEN an appropriate intent gets sent out + val intent = serviceStateIntent(subId = -1, emergencyOnly = false) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + intent, + ) + runCurrent() + + // THEN the repo's state is updated + val expected = ServiceStateModel(isEmergencyOnly = false) + assertThat(underTest.deviceServiceState.value).isEqualTo(expected) + } + + @Test + fun testDeviceServiceStateFromBroadcast_followsSubIdNegativeOne() = + testScope.runTest { + // device based state tracks -1 + val intent = serviceStateIntent(subId = -1, emergencyOnly = false) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + intent, + ) + runCurrent() + + val deviceBasedState = ServiceStateModel(isEmergencyOnly = false) + assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState) + + // ... and ignores any other subId + val intent2 = serviceStateIntent(subId = 1, emergencyOnly = true) + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + intent2, + ) + runCurrent() + + assertThat(underTest.deviceServiceState.value).isEqualTo(deviceBasedState) + } + @Test @Ignore("b/333912012") fun testConnectionCache_clearsInvalidSubscriptions() = @@ -1447,5 +1540,24 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE) whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(true) } + + /** + * To properly mimic telephony manager, create a service state, and then turn it into an + * intent + */ + private fun serviceStateIntent( + subId: Int, + emergencyOnly: Boolean = false, + ): Intent { + val serviceState = ServiceState().apply { isEmergencyOnly = emergencyOnly } + + val bundle = Bundle() + serviceState.fillInNotifierBundle(bundle) + + return Intent(Intent.ACTION_SERVICE_STATE).apply { + putExtras(bundle) + putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId) + } + } } } 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..58d9ee3935fd 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 @@ -28,6 +28,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository @@ -42,14 +43,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 +66,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 +111,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 +126,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 +136,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 +147,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 +164,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 +183,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 +203,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 +223,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 +244,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 +266,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 +286,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 +409,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 +757,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 +765,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 +846,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 @@ -919,6 +889,22 @@ class MobileIconsInteractorTest : SysuiTestCase() { assertThat(interactor1).isSameInstanceAs(interactor2) } + @Test + fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() = + testScope.runTest { + val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode) + + connectionsRepository.deviceServiceState.value = + ServiceStateModel(isEmergencyOnly = true) + + assertThat(latest).isTrue() + + connectionsRepository.deviceServiceState.value = + ServiceStateModel(isEmergencyOnly = false) + + assertThat(latest).isFalse() + } + /** * Convenience method for creating a pair of subscriptions to test the filteredSubscriptions * flow. diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt new file mode 100644 index 000000000000..7ca3b1c425d3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.data + +import android.telephony.satellite.SatelliteManager +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.demomode.DemoMode +import com.android.systemui.demomode.DemoModeController +import com.android.systemui.log.core.FakeLogBuffer +import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteDataSource +import com.android.systemui.statusbar.pipeline.satellite.data.demo.DemoDeviceBasedSatelliteRepository +import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl +import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState +import com.android.systemui.util.mockito.kotlinArgumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.mockito.Mockito.verify + +@SmallTest +class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() { + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val demoModeController = + mock<DemoModeController>().apply { whenever(this.isInDemoMode).thenReturn(false) } + private val satelliteManager = mock<SatelliteManager>() + private val systemClock = FakeSystemClock() + + private val realImpl = + DeviceBasedSatelliteRepositoryImpl( + Optional.of(satelliteManager), + testDispatcher, + testScope.backgroundScope, + FakeLogBuffer.Factory.create(), + systemClock, + ) + private val demoDataSource = + mock<DemoDeviceBasedSatelliteDataSource>().also { + whenever(it.satelliteEvents) + .thenReturn( + MutableStateFlow( + DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent( + connectionState = SatelliteConnectionState.Unknown, + signalStrength = 0, + ) + ) + ) + } + private val demoImpl = + DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope) + + private val underTest = + DeviceBasedSatelliteRepositorySwitcher( + realImpl, + demoImpl, + demoModeController, + testScope.backgroundScope, + ) + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun switcherActiveRepo_updatesWhenDemoModeChanges() = + testScope.runTest { + assertThat(underTest.activeRepo.value).isSameInstanceAs(realImpl) + + val latest by collectLastValue(underTest.activeRepo) + runCurrent() + + startDemoMode() + + assertThat(latest).isSameInstanceAs(demoImpl) + + finishDemoMode() + + assertThat(latest).isSameInstanceAs(realImpl) + } + + private fun startDemoMode() { + whenever(demoModeController.isInDemoMode).thenReturn(true) + getDemoModeCallback().onDemoModeStarted() + } + + private fun finishDemoMode() { + whenever(demoModeController.isInDemoMode).thenReturn(false) + getDemoModeCallback().onDemoModeFinished() + } + + private fun getDemoModeCallback(): DemoMode { + val captor = kotlinArgumentCaptor<DemoMode>() + verify(demoModeController).addCallback(captor.capture()) + return captor.value + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt new file mode 100644 index 000000000000..f77fd1999007 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.pipeline.satellite.data.demo + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before + +@SmallTest +class DemoDeviceBasedSatelliteRepositoryTest : SysuiTestCase() { + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private val fakeSatelliteEvents = + MutableStateFlow( + DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent( + connectionState = SatelliteConnectionState.Unknown, + signalStrength = 0, + ) + ) + + private lateinit var dataSource: DemoDeviceBasedSatelliteDataSource + + private lateinit var underTest: DemoDeviceBasedSatelliteRepository + + @Before + fun setUp() { + dataSource = + mock<DemoDeviceBasedSatelliteDataSource>().also { + whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents) + } + + underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope) + } + + @Test + fun startProcessing_getsNewUpdates() = + testScope.runTest { + val latestConnection by collectLastValue(underTest.connectionState) + val latestSignalStrength by collectLastValue(underTest.signalStrength) + + underTest.startProcessingCommands() + + fakeSatelliteEvents.value = + DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent( + connectionState = SatelliteConnectionState.On, + signalStrength = 3, + ) + + assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On) + assertThat(latestSignalStrength).isEqualTo(3) + + fakeSatelliteEvents.value = + DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent( + connectionState = SatelliteConnectionState.Connected, + signalStrength = 4, + ) + + assertThat(latestConnection).isEqualTo(SatelliteConnectionState.Connected) + assertThat(latestSignalStrength).isEqualTo(4) + } + + @Test + fun stopProcessing_stopsGettingUpdates() = + testScope.runTest { + val latestConnection by collectLastValue(underTest.connectionState) + val latestSignalStrength by collectLastValue(underTest.signalStrength) + + underTest.startProcessingCommands() + + fakeSatelliteEvents.value = + DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent( + connectionState = SatelliteConnectionState.On, + signalStrength = 3, + ) + assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On) + assertThat(latestSignalStrength).isEqualTo(3) + + underTest.stopProcessingCommands() + + // WHEN new values are emitted + fakeSatelliteEvents.value = + DemoDeviceBasedSatelliteDataSource.DemoSatelliteEvent( + connectionState = SatelliteConnectionState.Connected, + signalStrength = 4, + ) + + // THEN they're not collected because we stopped processing commands, so the old values + // are still present + assertThat(latestConnection).isEqualTo(SatelliteConnectionState.On) + assertThat(latestSignalStrength).isEqualTo(3) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt index 77e48bff04de..6b0ad4bdb770 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt @@ -156,7 +156,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { verify(satelliteManager).registerForNtnSignalStrengthChanged(any(), capture()) } - assertThat(latest).isNull() + assertThat(latest).isEqualTo(0) callback.onNtnSignalStrengthChanged(NtnSignalStrength(1)) assertThat(latest).isEqualTo(1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt index 405e3ed807d5..d303976612c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractorTest.kt @@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest import com.android.internal.telephony.flags.Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.log.core.FakeLogBuffer import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository @@ -71,6 +72,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) } @@ -114,6 +116,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) val latest by collectLastValue(underTest.isSatelliteAllowed) @@ -162,6 +165,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) val latest by collectLastValue(underTest.connectionState) @@ -218,6 +222,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) val latest by collectLastValue(underTest.signalStrength) @@ -238,25 +243,97 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_noConnections_yes() = + fun areAllConnectionsOutOfService_noConnections_noDeviceEmergencyCalls_yes() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) // GIVEN, 0 connections + // GIVEN, device is not in emergency calls only mode + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false + // THEN the value is propagated to this interactor assertThat(latest).isTrue() } @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_yes() = + fun areAllConnectionsOutOfService_noConnections_deviceEmergencyCalls_yes() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 0 connections + + // GIVEN, device is in emergency calls only mode + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true + + // THEN the value is propagated to this interactor + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_noDeviceEmergencyCalls_yes() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 1 connections + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + // GIVEN, no device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false + + // WHEN connection is in service + i1.isInService.value = true + i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false + + // THEN we are considered NOT to be OOS + assertThat(latest).isFalse() + + // WHEN the connection disappears + iconsInteractor.icons.value = listOf() + + // THEN we are back to OOS + assertThat(latest).isTrue() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_oneConnectionInService_thenLost_deviceEmergencyCalls_no() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 1 connections + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + // GIVEN, device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true + + // WHEN one connection is in service + i1.isInService.value = true + i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false + + // THEN we are considered NOT to be OOS + assertThat(latest).isFalse() + + // WHEN the connection disappears + iconsInteractor.icons.value = listOf() + + // THEN we are still NOT in OOS, due to device-based emergency calls + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_noDeviceEmergencyCalls_yes() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) // GIVEN, 2 connections val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) + // GIVEN, no device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false // WHEN all of the connections are OOS and none are NTN i1.isInService.value = false @@ -272,13 +349,39 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_twoConnectionsOos_oneNtn_no() = + fun areAllConnectionsOutOfService_twoConnectionsOos_nonNtn_deviceEmergencyCalls_no() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 2 connections + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) + // GIVEN, device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true + + // WHEN all of the connections are OOS and none are NTN + i1.isInService.value = false + i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false + i2.isInService.value = false + i2.isEmergencyOnly.value = false + i2.isNonTerrestrial.value = false + + // THEN we are not considered OOS due to device based emergency calling + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_twoConnectionsOos_noDeviceEmergencyCalls_oneNtn_no() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) // GIVEN, 2 connections val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) val i2 = iconsInteractor.getMobileConnectionInteractorForSubId(2) + // GIVEN, no device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false // WHEN all of the connections are OOS and one is NTN i1.isInService.value = false @@ -296,12 +399,14 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_yes() = + fun areAllConnectionsOutOfService_oneConnectionOos_noDeviceEmergencyCalls_nonNtn_yes() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) // GIVEN, 1 connection val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + // GIVEN, no device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = false // WHEN all of the connections are OOS i1.isInService.value = false @@ -314,7 +419,27 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { @Test @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) - fun areAllConnectionsOutOfService_oneConnectionOos_ntn_yes() = + fun areAllConnectionsOutOfService_oneConnectionOos_nonNtn_no() = + testScope.runTest { + val latest by collectLastValue(underTest.areAllConnectionsOutOfService) + + // GIVEN, 1 connection + val i1 = iconsInteractor.getMobileConnectionInteractorForSubId(1) + // GIVEN, device-based emergency calls + iconsInteractor.isDeviceInEmergencyCallsOnlyMode.value = true + + // WHEN all of the connections are OOS + i1.isInService.value = false + i1.isEmergencyOnly.value = false + i1.isNonTerrestrial.value = false + + // THEN the value is propagated to this interactor + assertThat(latest).isFalse() + } + + @Test + @EnableFlags(FLAG_OEM_ENABLED_SATELLITE_FLAG) + fun areAllConnectionsOutOfService_oneConnectionOos_ntn_no() = testScope.runTest { val latest by collectLastValue(underTest.areAllConnectionsOutOfService) @@ -416,6 +541,7 @@ class DeviceBasedSatelliteInteractorTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) val latest by collectLastValue(underTest.areAllConnectionsOutOfService) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt index ceaae9e02e87..43b95688729c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt @@ -75,6 +75,7 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() { deviceProvisioningInteractor, wifiInteractor, testScope.backgroundScope, + FakeLogBuffer.Factory.create(), ) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt index d1c38f6cbea7..0a5e63085b9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapterTest.kt @@ -22,6 +22,7 @@ import android.graphics.Bitmap import android.os.UserHandle import android.view.View import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.qs.user.UserSwitchDialogController @@ -33,14 +34,13 @@ import java.lang.ref.WeakReference import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class BaseUserSwitcherAdapterTest : SysuiTestCase() { @Mock private lateinit var controller: UserSwitcherController diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java index 9d4f1fc04594..ed8843b1d9f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java @@ -38,13 +38,13 @@ import android.os.PowerManager; import android.os.PowerSaveState; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; -import android.view.View; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.StaticInOrder; import com.android.settingslib.fuelgauge.BatterySaverUtils; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.Expandable; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; @@ -72,7 +72,7 @@ public class BatteryControllerTest extends SysuiTestCase { @Mock private PowerManager mPowerManager; @Mock private BroadcastDispatcher mBroadcastDispatcher; @Mock private DemoModeController mDemoModeController; - @Mock private View mView; + @Mock private Expandable mExpandable; @Mock private UsbPort mUsbPort; @Mock private UsbManager mUsbManager; @Mock private UsbPortStatus mUsbPortStatus; @@ -175,8 +175,8 @@ public class BatteryControllerTest extends SysuiTestCase { @Test public void testBatteryUtilsCalledOnSetPowerSaveMode() { - mBatteryController.setPowerSaveMode(true, mView); - mBatteryController.setPowerSaveMode(false, mView); + mBatteryController.setPowerSaveMode(true, mExpandable); + mBatteryController.setPowerSaveMode(false, mExpandable); StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class)); inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true, @@ -187,21 +187,21 @@ public class BatteryControllerTest extends SysuiTestCase { @Test public void testSaveViewReferenceWhenSettingPowerSaveMode() { - mBatteryController.setPowerSaveMode(false, mView); + mBatteryController.setPowerSaveMode(false, mExpandable); - Assert.assertNull(mBatteryController.getLastPowerSaverStartView()); + Assert.assertNull(mBatteryController.getLastPowerSaverStartExpandable()); - mBatteryController.setPowerSaveMode(true, mView); + mBatteryController.setPowerSaveMode(true, mExpandable); - Assert.assertSame(mView, mBatteryController.getLastPowerSaverStartView().get()); + Assert.assertSame(mExpandable, mBatteryController.getLastPowerSaverStartExpandable().get()); } @Test public void testClearViewReference() { - mBatteryController.setPowerSaveMode(true, mView); - mBatteryController.clearLastPowerSaverStartView(); + mBatteryController.setPowerSaveMode(true, mExpandable); + mBatteryController.clearLastPowerSaverStartExpandable(); - Assert.assertNull(mBatteryController.getLastPowerSaverStartView()); + Assert.assertNull(mBatteryController.getLastPowerSaverStartExpandable()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt index fb4ccb52929a..c22c62825d04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt @@ -20,8 +20,8 @@ import android.content.ComponentName import android.content.Context import android.content.pm.ServiceInfo import android.provider.Settings -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R @@ -60,7 +60,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyObject @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class DeviceControlsControllerImplTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index 2955162f80c2..f6e07d3d621e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -29,10 +29,10 @@ import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; import android.os.UserHandle; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableContentResolver; import android.testing.TestableResources; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -51,7 +51,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt index 1c54263cb0ce..80cc6eca8405 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt @@ -20,8 +20,8 @@ import android.content.pm.PackageManager import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraManager import android.hardware.camera2.impl.CameraMetadataNative +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dump.DumpManager @@ -46,7 +46,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class FlashlightControllerImplTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt index 0bd6a685708b..9f74915b4d1b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.statusbar.policy import android.content.Context import android.content.pm.UserInfo import android.graphics.Bitmap -import android.testing.AndroidTestingRunner import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.util.UserIcons import com.android.systemui.res.R @@ -44,7 +44,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt index b03edaf8ebd5..4b14e642063a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt @@ -23,7 +23,7 @@ import android.content.pm.PackageManager import android.net.Uri import android.os.Handler import android.safetycenter.SafetyCenterManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any @@ -44,7 +44,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SafetyControllerTest : SysuiTestCase() { private val TEST_PC_PKG = "testPermissionControllerPackageName" @@ -188,4 +188,4 @@ class SafetyControllerTest : SysuiTestCase() { assertThat(called).isTrue() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt index 3e20f689569e..81f095041fbc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerFlagDisabledTest.kt @@ -22,7 +22,7 @@ import android.media.projection.MediaProjectionManager import android.os.Handler import android.platform.test.annotations.DisableFlags import android.telephony.TelephonyManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.server.notification.Flags import com.android.systemui.SysuiTestCase @@ -38,7 +38,7 @@ import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @DisableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) class SensitiveNotificationProtectionControllerFlagDisabledTest : SysuiTestCase() { private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt index dbc2e3471c28..0249ab8fc9eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.statusbar.policy import android.service.quickaccesswallet.QuickAccessWalletClient -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -35,7 +35,7 @@ import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class WalletControllerImplTest : SysuiTestCase() { @Mock 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..d88289d9132c 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 @@ -21,12 +21,18 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.andSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.domain.interactor.keyguardStatusBarInteractor +import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository +import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository +import com.android.systemui.statusbar.notification.stack.data.repository.setNotifications +import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.batteryController import com.android.systemui.testKosmos @@ -47,10 +53,14 @@ 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 faceAuthRepository by lazy { kosmos.fakeDeviceEntryFaceAuthRepository } + private val headsUpRepository by lazy { kosmos.headsUpNotificationRepository } + private val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor } private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } + private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository } private val keyguardInteractor by lazy { kosmos.keyguardInteractor } private val keyguardStatusBarInteractor by lazy { kosmos.keyguardStatusBarInteractor } private val batteryController = kosmos.batteryController @@ -66,7 +76,7 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestC } init { - mSetFlagsRule.setFlagsParameterization(flags!!) + mSetFlagsRule.setFlagsParameterization(flags) } @Before @@ -74,6 +84,7 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestC underTest = KeyguardStatusBarViewModel( testScope.backgroundScope, + headsUpNotificationInteractor, keyguardInteractor, keyguardStatusBarInteractor, batteryController, @@ -112,7 +123,22 @@ class KeyguardStatusBarViewModelTest(flags: FlagsParameterization?) : SysuiTestC } @Test - fun isVisible_statusBarStateKeyguard_andNotDozing_true() = + fun isVisible_headsUpStatusBarShown_false() = + testScope.runTest { + val latest by collectLastValue(underTest.isVisible) + + // WHEN HUN displayed on the bypass lock screen + headsUpRepository.setNotifications(FakeHeadsUpRowRepository("key 0", isPinned = true)) + keyguardTransitionRepository.emitInitialStepsFromOff(KeyguardState.LOCKSCREEN) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + faceAuthRepository.isBypassEnabled.value = true + + // THEN KeyguardStatusBar is NOT visible to make space for HeadsUpStatusBar + assertThat(latest).isFalse() + } + + @Test + fun isVisible_statusBarStateKeyguard_andNotDozing_andNotShowingHeadsUpStatusBar_true() = testScope.runTest { val latest by collectLastValue(underTest.isVisible) diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index ed7c9568a9db..a5e7a67b21a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -995,9 +995,11 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { .setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); // All dynamic colors were added twice: light and dark them // All fixed colors were added once + // All custom dynamic tokens added twice verify(dynamic, times( DynamicColors.allDynamicColorsMapped(false).size() * 2 - + DynamicColors.getFixedColorsMapped(false).size()) + + DynamicColors.getFixedColorsMapped(false).size() + + DynamicColors.getCustomColorsMapped(false).size() * 2) ).setResourceValue(any(String.class), eq(TYPE_INT_COLOR_ARGB8), anyInt(), eq(null)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt index fd368eb07b5b..eaef0073b6fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/RotationChangeProviderTest.kt @@ -18,8 +18,11 @@ package com.android.systemui.unfold.updates import android.content.Context import android.hardware.display.DisplayManager +import android.os.HandlerThread import android.os.Looper +import android.os.Process import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper import android.view.Display import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -40,6 +43,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest +@RunWithLooper class RotationChangeProviderTest : SysuiTestCase() { private lateinit var rotationChangeProvider: RotationChangeProvider @@ -48,7 +52,10 @@ class RotationChangeProviderTest : SysuiTestCase() { @Mock lateinit var listener: RotationListener @Mock lateinit var display: Display @Captor lateinit var displayListener: ArgumentCaptor<DisplayManager.DisplayListener> - private val fakeHandler = FakeHandler(Looper.getMainLooper()) + private val bgThread = + HandlerThread("UnfoldBgTest", Process.THREAD_PRIORITY_FOREGROUND).apply { start() } + private val bgHandler = FakeHandler(bgThread.looper) + private val callbackHandler = FakeHandler(Looper.getMainLooper()) private lateinit var spyContext: Context @@ -57,9 +64,10 @@ class RotationChangeProviderTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) spyContext = spy(context) whenever(spyContext.display).thenReturn(display) - rotationChangeProvider = RotationChangeProvider(displayManager, spyContext, fakeHandler) + rotationChangeProvider = + RotationChangeProvider(displayManager, spyContext, bgHandler, callbackHandler) rotationChangeProvider.addCallback(listener) - fakeHandler.dispatchQueuedMessages() + bgHandler.dispatchQueuedMessages() verify(displayManager).registerDisplayListener(displayListener.capture(), any()) } @@ -76,7 +84,7 @@ class RotationChangeProviderTest : SysuiTestCase() { verify(listener).onRotationChanged(42) rotationChangeProvider.removeCallback(listener) - fakeHandler.dispatchQueuedMessages() + bgHandler.dispatchQueuedMessages() sendRotationUpdate(43) verify(displayManager).unregisterDisplayListener(any()) @@ -86,6 +94,6 @@ class RotationChangeProviderTest : SysuiTestCase() { private fun sendRotationUpdate(newRotation: Int) { whenever(display.rotation).thenReturn(newRotation) displayListener.allValues.forEach { it.onDisplayChanged(display.displayId) } - fakeHandler.dispatchQueuedMessages() + callbackHandler.dispatchQueuedMessages() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt index 948670f95f97..01795e92d141 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.user.domain.interactor import android.app.admin.DevicePolicyManager +import android.content.Context import android.content.pm.UserInfo import android.os.UserHandle import android.os.UserManager @@ -59,6 +60,7 @@ class GuestUserInteractorTest : SysuiTestCase() { @Mock private lateinit var switchUser: (Int) -> Unit @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver + @Mock private lateinit var otherContext: Context private lateinit var underTest: GuestUserInteractor @@ -74,28 +76,30 @@ class GuestUserInteractorTest : SysuiTestCase() { repository = FakeUserRepository() repository.setUserInfos(ALL_USERS) - underTest = - GuestUserInteractor( - applicationContext = context, - applicationScope = scope, - mainDispatcher = IMMEDIATE, - backgroundDispatcher = IMMEDIATE, - manager = manager, - repository = repository, - deviceProvisionedController = deviceProvisionedController, - devicePolicyManager = devicePolicyManager, - refreshUsersScheduler = - RefreshUsersScheduler( - applicationScope = scope, - mainDispatcher = IMMEDIATE, - repository = repository, - ), - uiEventLogger = uiEventLogger, - resumeSessionReceiver = resumeSessionReceiver, - resetOrExitSessionReceiver = resetOrExitSessionReceiver, - ) + underTest = initGuestUserInteractor(context) } + private fun initGuestUserInteractor(context: Context) = + GuestUserInteractor( + applicationContext = context, + applicationScope = scope, + mainDispatcher = IMMEDIATE, + backgroundDispatcher = IMMEDIATE, + manager = manager, + repository = repository, + deviceProvisionedController = deviceProvisionedController, + devicePolicyManager = devicePolicyManager, + refreshUsersScheduler = + RefreshUsersScheduler( + applicationScope = scope, + mainDispatcher = IMMEDIATE, + repository = repository, + ), + uiEventLogger = uiEventLogger, + resumeSessionReceiver = resumeSessionReceiver, + resetOrExitSessionReceiver = resetOrExitSessionReceiver, + ) + @Test fun registersBroadcastReceivers() { verify(resumeSessionReceiver).register() @@ -103,6 +107,16 @@ class GuestUserInteractorTest : SysuiTestCase() { } @Test + fun registersBroadcastReceiversOnlyForSystemUser() { + for (i in 1..5) { + whenever(otherContext.userId).thenReturn(UserHandle.MIN_SECONDARY_USER_ID + i) + initGuestUserInteractor(otherContext) + } + verify(resumeSessionReceiver).register() + verify(resetOrExitSessionReceiver).register() + } + + @Test fun onDeviceBootCompleted_allowedToAdd_createGuest() = runBlocking(IMMEDIATE) { setAllowedToAdd() diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt index 3dee093bd594..96c6eb8107df 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt @@ -887,6 +887,46 @@ class UserSwitcherInteractorTest : SysuiTestCase() { } @Test + fun removeGuestUser_shouldNotShowExitGuestDialog() { + createUserInteractor() + testScope.runTest { + val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true) + userRepository.setUserInfos(listOf(userInfo, guestUserInfo)) + userRepository.setSelectedUserInfo(guestUserInfo) + + whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true) + underTest.removeGuestUser(guestUserInfo.id, userInfo.id) + runCurrent() + + verify(manager).markGuestForDeletion(guestUserInfo.id) + verify(activityManager).switchUser(userInfo.id) + assertThat(collectLastValue(underTest.dialogShowRequests)()).isNull() + } + } + + @Test + fun resetGuestUser_shouldNotShowExitGuestDialog() { + createUserInteractor() + testScope.runTest { + val (userInfo, guestUserInfo) = createUserInfos(count = 2, includeGuest = true) + val otherGuestUserInfo = createUserInfos(count = 1, includeGuest = true)[0] + userRepository.setUserInfos(listOf(userInfo, guestUserInfo)) + userRepository.setSelectedUserInfo(guestUserInfo) + + whenever(manager.markGuestForDeletion(guestUserInfo.id)).thenReturn(true) + whenever(manager.createGuest(any())).thenReturn(otherGuestUserInfo) + underTest.removeGuestUser(guestUserInfo.id, UserHandle.USER_NULL) + runCurrent() + + verify(manager).markGuestForDeletion(guestUserInfo.id) + verify(manager).createGuest(any()) + verify(activityManager).switchUser(otherGuestUserInfo.id) + assertThat(collectLastValue(underTest.dialogShowRequests)()) + .isEqualTo(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true)) + } + } + + @Test fun showUserSwitcher_fullScreenDisabled_showsDialogSwitcher() { createUserInteractor() testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt index 31848a67698c..e1dcb145f20b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.util import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.wm.shell.common.FloatingContentCoordinator @@ -14,7 +14,7 @@ import org.junit.Test import org.junit.runner.RunWith @TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class FloatingContentCoordinatorTest : SysuiTestCase() { @@ -198,12 +198,11 @@ class FloatingContentCoordinatorTest : SysuiTestCase() { } /** - * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a - * Rect when needed. + * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a Rect when + * needed. */ - inner class FloatingRect( - private val underlyingRect: Rect - ) : FloatingContentCoordinator.FloatingContent { + inner class FloatingRect(private val underlyingRect: Rect) : + FloatingContentCoordinator.FloatingContent { override fun moveToBounds(bounds: Rect) { underlyingRect.set(bounds) } @@ -216,4 +215,4 @@ class FloatingContentCoordinatorTest : SysuiTestCase() { return underlyingRect } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt index 436f5b827ec6..457f2bb5d826 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt @@ -19,12 +19,13 @@ package com.android.systemui.util import android.content.BroadcastReceiver import android.content.IntentFilter import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.lifecycle.Observer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher +import java.util.concurrent.Executor import org.junit.After import org.junit.Assert.assertTrue import org.junit.Before @@ -38,10 +39,9 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -import java.util.concurrent.Executor @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class RingerModeLiveDataTest : SysuiTestCase() { @@ -52,16 +52,11 @@ class RingerModeLiveDataTest : SysuiTestCase() { private val INTENT = "INTENT" } - @Mock - private lateinit var broadcastDispatcher: BroadcastDispatcher - @Mock - private lateinit var valueSupplier: () -> Int - @Mock - private lateinit var observer: Observer<Int> - @Captor - private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> - @Captor - private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter> + @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var valueSupplier: () -> Int + @Mock private lateinit var observer: Observer<Int> + @Captor private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> + @Captor private lateinit var intentFilterCaptor: ArgumentCaptor<IntentFilter> // Run everything immediately private val executor = Executor { it.run() } @@ -88,14 +83,14 @@ class RingerModeLiveDataTest : SysuiTestCase() { fun testOnActive_broadcastRegistered() { liveData.observeForever(observer) verify(broadcastDispatcher) - .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any()) + .registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any()) } @Test fun testOnActive_intentFilterHasIntent() { liveData.observeForever(observer) - verify(broadcastDispatcher).registerReceiver(any(), capture(intentFilterCaptor), any(), - any(), anyInt(), any()) + verify(broadcastDispatcher) + .registerReceiver(any(), capture(intentFilterCaptor), any(), any(), anyInt(), any()) assertTrue(intentFilterCaptor.value.hasAction(INTENT)) } @@ -111,4 +106,4 @@ class RingerModeLiveDataTest : SysuiTestCase() { liveData.removeObserver(observer) verify(broadcastDispatcher).unregisterReceiver(any()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt index b13cb72dc944..6271904b2f04 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.util import android.app.WallpaperInfo import android.app.WallpaperManager import android.os.IBinder -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.view.ViewRootImpl +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.eq @@ -32,36 +32,30 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.anyFloat import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.doThrow -import org.mockito.Mockito.times -import org.mockito.Mockito.verify import org.mockito.Mockito.mock import org.mockito.Mockito.never +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @SmallTest class WallpaperControllerTest : SysuiTestCase() { - @Mock - private lateinit var wallpaperManager: WallpaperManager - @Mock - private lateinit var root: View - @Mock - private lateinit var viewRootImpl: ViewRootImpl - @Mock - private lateinit var windowToken: IBinder + @Mock private lateinit var wallpaperManager: WallpaperManager + @Mock private lateinit var root: View + @Mock private lateinit var viewRootImpl: ViewRootImpl + @Mock private lateinit var windowToken: IBinder private val wallpaperRepository = FakeWallpaperRepository() - @JvmField - @Rule - val mockitoRule = MockitoJUnit.rule() + @JvmField @Rule val mockitoRule = MockitoJUnit.rule() private lateinit var wallaperController: WallpaperController @@ -92,9 +86,7 @@ class WallpaperControllerTest : SysuiTestCase() { @Test fun setUnfoldTransitionZoom_defaultUnfoldTransitionIsDisabled_doesNotUpdateWallpaperZoom() { - wallpaperRepository.wallpaperInfo.value = createWallpaperInfo( - useDefaultTransition = false - ) + wallpaperRepository.wallpaperInfo.value = createWallpaperInfo(useDefaultTransition = false) wallaperController.setUnfoldTransitionZoom(0.5f) @@ -130,7 +122,8 @@ class WallpaperControllerTest : SysuiTestCase() { @Test fun setNotificationZoom_exceptionWhenUpdatingZoom_doesNotFail() { - doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager) + doThrow(IllegalArgumentException("test exception")) + .`when`(wallpaperManager) .setWallpaperZoomOut(any(), anyFloat()) wallaperController.setNotificationShadeZoom(0.5f) @@ -140,8 +133,7 @@ class WallpaperControllerTest : SysuiTestCase() { private fun createWallpaperInfo(useDefaultTransition: Boolean = true): WallpaperInfo { val info = mock(WallpaperInfo::class.java) - whenever(info.shouldUseDefaultUnfoldTransition()) - .thenReturn(useDefaultTransition) + whenever(info.shouldUseDefaultUnfoldTransition()).thenReturn(useDefaultTransition) return info } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt index 92afb038b321..b26598c80478 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt @@ -16,13 +16,16 @@ package com.android.systemui.util.animation +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat -import org.junit.Test import java.lang.IllegalArgumentException +import org.junit.runner.RunWith +import org.junit.Test @SmallTest +@RunWith(AndroidJUnit4::class) class AnimationUtilTest : SysuiTestCase() { @Test fun getMsForFrames_5frames_returns83() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java index 9dfa14dd0784..7dfac0ab2650 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutorTest.java @@ -22,8 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -39,7 +38,7 @@ import java.util.ArrayList; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class FakeExecutorTest extends SysuiTestCase { @Before public void setUp() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java index 78fc6803ea7e..48fb74514b01 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/MessageRouterImplTest.java @@ -24,8 +24,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -39,7 +38,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class MessageRouterImplTest extends SysuiTestCase { private static final int MESSAGE_A = 0; private static final int MESSAGE_B = 1; 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..7ec420f0b6da 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 @@ -15,7 +15,7 @@ */ package com.android.systemui.util.concurrency -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.time.FakeSystemClock @@ -26,7 +26,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MockExecutorHandlerTest : SysuiTestCase() { /** Test FakeExecutor that receives non-delayed items to execute. */ @Test @@ -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/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java index 00f37ae6f6cb..13fff29d89ed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java @@ -18,8 +18,7 @@ package com.android.systemui.util.concurrency; import static com.google.common.truth.Truth.assertThat; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -30,7 +29,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class RepeatableExecutorTest extends SysuiTestCase { private static final int DELAY = 100; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java index b367a603ec67..37015e30781e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionalCoreStartableTest.java @@ -23,8 +23,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.CoreStartable; @@ -44,7 +43,7 @@ import java.util.HashSet; import java.util.Set; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ConditionalCoreStartableTest extends SysuiTestCase { public static class FakeConditionalCoreStartable extends ConditionalCoreStartable { interface Callback { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt index ac357ea34be0..b8f581574848 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt @@ -4,7 +4,7 @@ import android.content.res.Resources import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ShapeDrawable -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -12,7 +12,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class DrawableSizeTest : SysuiTestCase() { @@ -32,14 +32,11 @@ class DrawableSizeTest : SysuiTestCase() { @Test fun testDownscaleToSize_drawableSmallerThanRequirement_unchanged() { - val drawable = BitmapDrawable(resources, - Bitmap.createBitmap( - resources.displayMetrics, - 150, - 150, - Bitmap.Config.ARGB_8888 - ) - ) + val drawable = + BitmapDrawable( + resources, + Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888) + ) val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300) assertThat(result).isSameInstanceAs(drawable) } @@ -48,14 +45,11 @@ class DrawableSizeTest : SysuiTestCase() { fun testDownscaleToSize_drawableLargerThanRequirementWithDensity_resized() { // This bitmap would actually fail to resize if the method doesn't check for // bitmap dimensions inside drawable. - val drawable = BitmapDrawable(resources, - Bitmap.createBitmap( - resources.displayMetrics, - 150, - 75, - Bitmap.Config.ARGB_8888 - ) - ) + val drawable = + BitmapDrawable( + resources, + Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888) + ) val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75) assertThat(result).isNotSameInstanceAs(drawable) @@ -65,9 +59,9 @@ class DrawableSizeTest : SysuiTestCase() { @Test fun testDownscaleToSize_drawableAnimated_unchanged() { - val drawable = resources.getDrawable(android.R.drawable.stat_sys_download, - resources.newTheme()) + val drawable = + resources.getDrawable(android.R.drawable.stat_sys_download, resources.newTheme()) val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isSameInstanceAs(drawable) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt index 7d0d57b4037a..e2ce50ccb6da 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.time.FakeSystemClock @@ -47,7 +47,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class PairwiseFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { @@ -89,7 +89,9 @@ class PairwiseFlowTest : SysuiTestCase() { initRun = true "initial" } - ) { prev: String, next: String -> "$prev|$next" } + ) { prev: String, next: String -> + "$prev|$next" + } ) .emitsExactly("initial|val1", "val1|val2") assertThat(initRun).isTrue() @@ -104,7 +106,9 @@ class PairwiseFlowTest : SysuiTestCase() { initRun = true "initial" } - ) { prev: String, next: String -> "$prev|$next" } + ) { prev: String, next: String -> + "$prev|$next" + } ) .emitsNothing() // Even though the flow will not emit anything, the initial value function should still get @@ -120,7 +124,9 @@ class PairwiseFlowTest : SysuiTestCase() { initRun = true "initial" } - ) { prev: String, next: String -> "$prev|$next" } + ) { prev: String, next: String -> + "$prev|$next" + } // Since the flow isn't collected, ensure [initialValueFun] isn't run. assertThat(initRun).isFalse() @@ -146,7 +152,7 @@ class PairwiseFlowTest : SysuiTestCase() { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SetChangesFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { @@ -198,7 +204,7 @@ class SetChangesFlowTest : SysuiTestCase() { } @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class SampleFlowTest : SysuiTestCase() { @Test fun simple() = runBlocking { @@ -240,7 +246,7 @@ class SampleFlowTest : SysuiTestCase() { @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class ThrottleFlowTest : SysuiTestCase() { @Test @@ -248,13 +254,16 @@ class ThrottleFlowTest : SysuiTestCase() { // Arrange val choreographer = createChoreographer(this) val output = mutableListOf<Int>() - val collectJob = backgroundScope.launch { - flow { - emit(1) - delay(1000) - emit(2) - }.throttle(1000, choreographer.fakeClock).toList(output) - } + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(1000) + emit(2) + } + .throttle(1000, choreographer.fakeClock) + .toList(output) + } // Act choreographer.advanceAndRun(0) @@ -283,13 +292,16 @@ class ThrottleFlowTest : SysuiTestCase() { // Arrange val choreographer = createChoreographer(this) val output = mutableListOf<Int>() - val collectJob = backgroundScope.launch { - flow { - emit(1) - delay(500) - emit(2) - }.throttle(1000, choreographer.fakeClock).toList(output) - } + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(500) + emit(2) + } + .throttle(1000, choreographer.fakeClock) + .toList(output) + } // Act choreographer.advanceAndRun(0) @@ -319,15 +331,18 @@ class ThrottleFlowTest : SysuiTestCase() { // Arrange val choreographer = createChoreographer(this) val output = mutableListOf<Int>() - val collectJob = backgroundScope.launch { - flow { - emit(1) - delay(500) - emit(2) - delay(500) - emit(3) - }.throttle(1000, choreographer.fakeClock).toList(output) - } + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(500) + emit(2) + delay(500) + emit(3) + } + .throttle(1000, choreographer.fakeClock) + .toList(output) + } // Act choreographer.advanceAndRun(0) @@ -357,15 +372,18 @@ class ThrottleFlowTest : SysuiTestCase() { // Arrange val choreographer = createChoreographer(this) val output = mutableListOf<Int>() - val collectJob = backgroundScope.launch { - flow { - emit(1) - delay(500) - emit(2) - delay(250) - emit(3) - }.throttle(1000, choreographer.fakeClock).toList(output) - } + val collectJob = + backgroundScope.launch { + flow { + emit(1) + delay(500) + emit(2) + delay(250) + emit(3) + } + .throttle(1000, choreographer.fakeClock) + .toList(output) + } // Act choreographer.advanceAndRun(0) @@ -391,15 +409,16 @@ class ThrottleFlowTest : SysuiTestCase() { collectJob.cancel() } - private fun createChoreographer(testScope: TestScope) = object { - val fakeClock = FakeSystemClock() + private fun createChoreographer(testScope: TestScope) = + object { + val fakeClock = FakeSystemClock() - fun advanceAndRun(millis: Long) { - fakeClock.advanceTime(millis) - testScope.advanceTimeBy(millis) - testScope.runCurrent() + fun advanceAndRun(millis: Long) { + fakeClock.advanceTime(millis) + testScope.advanceTimeBy(millis) + testScope.runCurrent() + } } - } } private fun <T> assertThatFlow(flow: Flow<T>) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt index 4ca1fd39682d..c31b287fddce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import java.util.concurrent.atomic.AtomicLong @@ -31,43 +31,42 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class IpcSerializerTest : SysuiTestCase() { private val serializer = IpcSerializer() @Ignore("b/253046405") @Test - fun serializeManyIncomingIpcs(): Unit = runBlocking(Dispatchers.Main.immediate) { - val processor = launch(start = CoroutineStart.LAZY) { serializer.process() } - withContext(Dispatchers.IO) { - val lastEvaluatedTime = AtomicLong(System.currentTimeMillis()) - // First, launch many serialization requests in parallel - repeat(100_000) { - launch(Dispatchers.Unconfined) { - val enqueuedTime = System.currentTimeMillis() - serializer.runSerialized { - val last = lastEvaluatedTime.getAndSet(enqueuedTime) - assertTrue( - "expected $last less than or equal to $enqueuedTime ", - last <= enqueuedTime, - ) + fun serializeManyIncomingIpcs(): Unit = + runBlocking(Dispatchers.Main.immediate) { + val processor = launch(start = CoroutineStart.LAZY) { serializer.process() } + withContext(Dispatchers.IO) { + val lastEvaluatedTime = AtomicLong(System.currentTimeMillis()) + // First, launch many serialization requests in parallel + repeat(100_000) { + launch(Dispatchers.Unconfined) { + val enqueuedTime = System.currentTimeMillis() + serializer.runSerialized { + val last = lastEvaluatedTime.getAndSet(enqueuedTime) + assertTrue( + "expected $last less than or equal to $enqueuedTime ", + last <= enqueuedTime, + ) + } } } + // Then, process them all in the order they came in. + processor.start() } - // Then, process them all in the order they came in. - processor.start() + // All done, stop processing + processor.cancel() } - // All done, stop processing - processor.cancel() - } @Test(timeout = 5000) fun serializeOnOneThread_doesNotDeadlock() = runBlocking { val job = launch { serializer.process() } - repeat(100) { - serializer.runSerializedBlocking { } - } + repeat(100) { serializer.runSerializedBlocking {} } job.cancel() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt index 2013bb0a547e..8bfff9c209e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/PackageManagerExtComponentEnabledTest.kt @@ -27,13 +27,13 @@ import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.junit.runners.Parameterized.Parameters import org.mockito.Mock import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters @SmallTest -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) internal class PackageManagerExtComponentEnabledTest(private val testCase: TestCase) : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt index 6848b836a348..b2f7c1aa0384 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt @@ -16,7 +16,7 @@ package com.android.systemui.util.kotlin -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -28,7 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class RaceSuspendTest : SysuiTestCase() { @Test fun raceSimple() = runBlocking { @@ -46,10 +46,11 @@ class RaceSuspendTest : SysuiTestCase() { @Test fun raceImmediate() = runBlocking { assertThat( - race<Int>( - { 1 }, - { 2 }, + race<Int>( + { 1 }, + { 2 }, + ) ) - ).isEqualTo(1) + .isEqualTo(1) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java index 84129beea92a..300c29852ead 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/PostureDependentProximitySensorTest.java @@ -26,9 +26,9 @@ import static org.mockito.Mockito.verify; import android.content.res.Resources; import android.hardware.Sensor; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -46,7 +46,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class PostureDependentProximitySensorTest extends SysuiTestCase { @Mock private Resources mResources; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java index 19dbf9aa3c13..5dd008ac10f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximityCheckTest.java @@ -23,9 +23,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; import java.util.function.Consumer; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ProximityCheckTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java index 5e7557896145..0eab74eb3cfb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplDualTest.java @@ -24,9 +24,9 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ProximitySensorImplDualTest extends SysuiTestCase { private ProximitySensor mProximitySensor; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java index 752cd3211161..f44c842ad2eb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorImplSingleTest.java @@ -21,9 +21,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.mockito.MockitoAnnotations; * Tests for ProximitySensor that rely on a single hardware sensor. */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ProximitySensorImplSingleTest extends SysuiTestCase { private ProximitySensor mProximitySensor; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java index 8d26c877f4cf..a54afad617bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java @@ -30,8 +30,8 @@ import android.content.Intent; import android.content.pm.UserInfo; import android.os.IBinder; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -49,7 +49,7 @@ import java.util.List; import java.util.Objects; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ObservableServiceConnectionTest extends SysuiTestCase { static class Foo { int mValue; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java index a2fd288ef33e..a70b00c8972d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PackageObserverTest.java @@ -24,8 +24,8 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -38,7 +38,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class PackageObserverTest extends SysuiTestCase { @Mock Context mContext; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java index 55c49ee4360d..ef10fdf63741 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java @@ -19,8 +19,7 @@ package com.android.systemui.util.service; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; - +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -37,7 +36,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class PersistentConnectionManagerTest extends SysuiTestCase { private static final int MAX_RETRIES = 5; private static final int RETRY_DELAY_MS = 1000; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java index f65caee24e34..99f6303ad73d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java @@ -28,8 +28,8 @@ import static org.mockito.Mockito.verify; import android.database.ContentObserver; import android.os.UserHandle; import android.provider.Settings; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -44,7 +44,7 @@ import java.util.Collection; import java.util.Map; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class FakeSettingsTest extends SysuiTestCase { @Mock ContentObserver mContentObserver; diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt new file mode 100644 index 000000000000..ab95707046d9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.settings + +import android.content.ContentResolver +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.provider.Settings.SettingNotFoundException +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.kotlin.eq + +/** Tests for [SettingsProxy]. */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +@TestableLooper.RunWithLooper +class SettingsProxyTest : SysuiTestCase() { + + private lateinit var mSettings: SettingsProxy + private lateinit var mContentObserver: ContentObserver + + @Before + fun setUp() { + mSettings = FakeSettingsProxy() + mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} + } + + @Test + fun registerContentObserver_inputString_success() { + mSettings.registerContentObserver(TEST_SETTING, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) + } + + @Test + fun registerContentObserver_inputString_notifyForDescendants_true() { + mSettings.registerContentObserver( + TEST_SETTING, + notifyForDescendants = true, + mContentObserver + ) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) + } + + @Test + fun registerContentObserver_inputUri_success() { + mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) + } + + @Test + fun registerContentObserver_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserver( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver + ) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) + } + + @Test + fun unregisterContentObserver() { + mSettings.unregisterContentObserver(mContentObserver) + verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver)) + } + + @Test + fun getString_keyPresent_returnValidValue() { + mSettings.putString(TEST_SETTING, "test") + assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test") + } + + @Test + fun getString_keyAbsent_returnEmptyValue() { + assertThat(mSettings.getString(TEST_SETTING)).isEmpty() + } + + @Test + fun getInt_keyPresent_returnValidValue() { + mSettings.putInt(TEST_SETTING, 2) + assertThat(mSettings.getInt(TEST_SETTING)).isEqualTo(2) + } + + @Test + fun getInt_keyPresent_nonIntegerValue_throwException() { + assertThrows(SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getInt(TEST_SETTING) + } + } + + @Test + fun getInt_keyAbsent_throwException() { + assertThrows(SettingNotFoundException::class.java) { mSettings.getInt(TEST_SETTING) } + } + + @Test + fun getInt_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5) + } + + @Test + fun getBool_keyPresent_returnValidValue() { + mSettings.putBool(TEST_SETTING, true) + assertThat(mSettings.getBool(TEST_SETTING)).isTrue() + } + + @Test + fun getBool_keyPresent_nonBooleanValue_throwException() { + assertThrows(SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getBool(TEST_SETTING) + } + } + + @Test + fun getBool_keyAbsent_throwException() { + assertThrows(SettingNotFoundException::class.java) { mSettings.getBool(TEST_SETTING) } + } + + @Test + fun getBool_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getBool(TEST_SETTING, false)).isEqualTo(false) + } + + @Test + fun getLong_keyPresent_returnValidValue() { + mSettings.putLong(TEST_SETTING, 1L) + assertThat(mSettings.getLong(TEST_SETTING)).isEqualTo(1L) + } + + @Test + fun getLong_keyPresent_nonLongValue_throwException() { + assertThrows(SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getLong(TEST_SETTING) + } + } + + @Test + fun getLong_keyAbsent_throwException() { + assertThrows(SettingNotFoundException::class.java) { mSettings.getLong(TEST_SETTING) } + } + + @Test + fun getLong_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L) + } + + @Test + fun getFloat_keyPresent_returnValidValue() { + mSettings.putFloat(TEST_SETTING, 2.5F) + assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F) + } + + @Test + fun getFloat_keyPresent_nonFloatValue_throwException() { + assertThrows(SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getFloat(TEST_SETTING) + } + } + + @Test + fun getFloat_keyAbsent_throwException() { + assertThrows(SettingNotFoundException::class.java) { mSettings.getFloat(TEST_SETTING) } + } + + @Test + fun getFloat_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F) + } + + private class FakeSettingsProxy : SettingsProxy { + + private val mContentResolver = mock(ContentResolver::class.java) + private val settingToValueMap: MutableMap<String, String> = mutableMapOf() + + override fun getContentResolver() = mContentResolver + + override fun getUriFor(name: String) = + Uri.parse(StringBuilder().append("content://settings/").append(name).toString()) + + override fun getString(name: String): String { + return settingToValueMap[name] ?: "" + } + + override fun putString(name: String, value: String): Boolean { + settingToValueMap[name] = value + return true + } + + override fun putString( + name: String, + value: String, + tag: String, + makeDefault: Boolean + ): Boolean { + settingToValueMap[name] = value + return true + } + } + + companion object { + private const val TEST_SETTING = "test_setting" + private val TEST_SETTING_URI = Uri.parse("content://settings/test_setting") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt new file mode 100644 index 000000000000..56328b933602 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.settings + +import android.content.ContentResolver +import android.content.pm.UserInfo +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.Looper +import android.provider.Settings +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.settings.UserTracker +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertThrows +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.kotlin.eq + +/** Tests for [UserSettingsProxy]. */ +@RunWith(AndroidTestingRunner::class) +@SmallTest +@TestableLooper.RunWithLooper +class UserSettingsProxyTest : SysuiTestCase() { + + private var mUserTracker = FakeUserTracker() + private var mSettings: UserSettingsProxy = FakeUserSettingsProxy(mUserTracker) + private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} + + @Before + fun setUp() { + mUserTracker.set( + listOf(UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_MAIN)), + selectedUserIndex = 0 + ) + } + + @Test + fun registerContentObserverForUser_inputString_success() { + mSettings.registerContentObserverForUser( + TEST_SETTING, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUser_inputString_notifyForDescendants_true() { + mSettings.registerContentObserverForUser( + TEST_SETTING, + notifyForDescendants = true, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(true), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUser_inputUri_success() { + mSettings.registerContentObserverForUser( + TEST_SETTING_URI, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUser_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserverForUser( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(true), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserver_inputUri_success() { + mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0)) + } + + @Test + fun registerContentObserver_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserver( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver + ) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver), eq(0)) + } + + @Test + fun getString_keyPresent_returnValidValue() { + mSettings.putString(TEST_SETTING, "test") + assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test") + } + + @Test + fun getString_keyAbsent_returnEmptyValue() { + assertThat(mSettings.getString(TEST_SETTING)).isEmpty() + } + + @Test + fun getStringForUser_multipleUsers_validResult() { + mSettings.putStringForUser(TEST_SETTING, "test", MAIN_USER_ID) + mSettings.putStringForUser(TEST_SETTING, "test1", SECONDARY_USER_ID) + assertThat(mSettings.getStringForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo("test") + assertThat(mSettings.getStringForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo("test1") + } + + @Test + fun getInt_keyPresent_returnValidValue() { + mSettings.putInt(TEST_SETTING, 2) + assertThat(mSettings.getInt(TEST_SETTING)).isEqualTo(2) + } + + @Test + fun getInt_keyPresent_nonIntegerValue_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getInt(TEST_SETTING) + } + } + + @Test + fun getInt_keyAbsent_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.getInt(TEST_SETTING) + } + } + + @Test + fun getInt_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getInt(TEST_SETTING, 5)).isEqualTo(5) + } + + @Test + fun getIntForUser_multipleUsers__validResult() { + mSettings.putIntForUser(TEST_SETTING, 1, MAIN_USER_ID) + mSettings.putIntForUser(TEST_SETTING, 2, SECONDARY_USER_ID) + assertThat(mSettings.getIntForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1) + assertThat(mSettings.getIntForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2) + } + + @Test + fun getBool_keyPresent_returnValidValue() { + mSettings.putBool(TEST_SETTING, true) + assertThat(mSettings.getBool(TEST_SETTING)).isTrue() + } + + @Test + fun getBool_keyPresent_nonBooleanValue_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getBool(TEST_SETTING) + } + } + + @Test + fun getBool_keyAbsent_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.getBool(TEST_SETTING) + } + } + + @Test + fun getBool_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getBool(TEST_SETTING, false)).isEqualTo(false) + } + + @Test + fun getBoolForUser_multipleUsers__validResult() { + mSettings.putBoolForUser(TEST_SETTING, true, MAIN_USER_ID) + mSettings.putBoolForUser(TEST_SETTING, false, SECONDARY_USER_ID) + assertThat(mSettings.getBoolForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(true) + assertThat(mSettings.getBoolForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(false) + } + + @Test + fun getLong_keyPresent_returnValidValue() { + mSettings.putLong(TEST_SETTING, 1L) + assertThat(mSettings.getLong(TEST_SETTING)).isEqualTo(1L) + } + + @Test + fun getLong_keyPresent_nonLongValue_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getLong(TEST_SETTING) + } + } + + @Test + fun getLong_keyAbsent_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.getLong(TEST_SETTING) + } + } + + @Test + fun getLong_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getLong(TEST_SETTING, 2L)).isEqualTo(2L) + } + + @Test + fun getLongForUser_multipleUsers__validResult() { + mSettings.putLongForUser(TEST_SETTING, 1L, MAIN_USER_ID) + mSettings.putLongForUser(TEST_SETTING, 2L, SECONDARY_USER_ID) + assertThat(mSettings.getLongForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1L) + assertThat(mSettings.getLongForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2L) + } + + @Test + fun getFloat_keyPresent_returnValidValue() { + mSettings.putFloat(TEST_SETTING, 2.5F) + assertThat(mSettings.getFloat(TEST_SETTING)).isEqualTo(2.5F) + } + + @Test + fun getFloat_keyPresent_nonFloatValue_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.putString(TEST_SETTING, "test") + mSettings.getFloat(TEST_SETTING) + } + } + + @Test + fun getFloat_keyAbsent_throwException() { + assertThrows(Settings.SettingNotFoundException::class.java) { + mSettings.getFloat(TEST_SETTING) + } + } + + @Test + fun getFloat_keyAbsent_returnDefaultValue() { + assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F) + } + + @Test + fun getFloatForUser_multipleUsers__validResult() { + mSettings.putFloatForUser(TEST_SETTING, 1F, MAIN_USER_ID) + mSettings.putFloatForUser(TEST_SETTING, 2F, SECONDARY_USER_ID) + assertThat(mSettings.getFloatForUser(TEST_SETTING, MAIN_USER_ID)).isEqualTo(1F) + assertThat(mSettings.getFloatForUser(TEST_SETTING, SECONDARY_USER_ID)).isEqualTo(2F) + } + + /** + * Fake implementation of [UserSettingsProxy]. + * + * This class uses a mock of [ContentResolver] to test the [ContentObserver] registration APIs. + */ + private class FakeUserSettingsProxy(override val userTracker: UserTracker) : UserSettingsProxy { + + private val mContentResolver = mock(ContentResolver::class.java) + private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> = + mutableMapOf() + + override fun getContentResolver() = mContentResolver + + override fun getUriFor(name: String) = + Uri.parse(StringBuilder().append(URI_PREFIX).append(name).toString()) + + override fun getStringForUser(name: String, userHandle: Int) = + userIdToSettingsValueMap[userHandle]?.get(name) ?: "" + + override fun putString( + name: String, + value: String, + overrideableByRestore: Boolean + ): Boolean { + userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value) + return true + } + + override fun putString( + name: String, + value: String, + tag: String, + makeDefault: Boolean + ): Boolean { + putStringForUser(name, value, DEFAULT_USER_ID) + return true + } + + override fun putStringForUser(name: String, value: String, userHandle: Int): Boolean { + userIdToSettingsValueMap[userHandle] = mutableMapOf(Pair(name, value)) + return true + } + + override fun putStringForUser( + name: String, + value: String, + tag: String?, + makeDefault: Boolean, + userHandle: Int, + overrideableByRestore: Boolean + ): Boolean { + userIdToSettingsValueMap[userHandle]?.set(name, value) + return true + } + + private companion object { + const val DEFAULT_USER_ID = 0 + const val URI_PREFIX = "content://settings/" + } + } + + private companion object { + const val MAIN_USER_ID = 10 + const val SECONDARY_USER_ID = 20 + const val TEST_SETTING = "test_setting" + val TEST_SETTING_URI = Uri.parse("content://settings/test_setting") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt index 913759f77013..88b2630bd78f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.util.settings.repository import android.content.pm.UserInfo +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -33,11 +34,10 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { private val dispatcher = StandardTestDispatcher() @@ -48,11 +48,12 @@ class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { @Before fun setup() { - repository = UserAwareSecureSettingsRepositoryImpl( - secureSettings, - userRepository, - dispatcher, - ) + repository = + UserAwareSecureSettingsRepositoryImpl( + secureSettings, + userRepository, + dispatcher, + ) userRepository.setUserInfos(USER_INFOS) setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER) setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER) @@ -105,4 +106,4 @@ class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() { val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0) val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt index 94100fe7f4c4..6637d5f8de92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/ui/AnimatedValueTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.util.ui -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -30,7 +30,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class AnimatedValueTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt index e3cd9b2d6eaf..3dcb82811408 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt @@ -19,17 +19,20 @@ package com.android.systemui.util.view import android.graphics.Rect import android.view.View import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.any import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith import org.mockito.Mockito.doAnswer import org.mockito.Mockito.spy import org.mockito.Mockito.`when` @SmallTest +@RunWith(AndroidJUnit4::class) class ViewUtilTest : SysuiTestCase() { private val viewUtil = ViewUtil() private lateinit var view: View @@ -45,11 +48,13 @@ class ViewUtilTest : SysuiTestCase() { location[1] = VIEW_TOP `when`(view.locationOnScreen).thenReturn(location) doAnswer { invocation -> - val pos = invocation.arguments[0] as IntArray - pos[0] = VIEW_LEFT - pos[1] = VIEW_TOP - null - }.`when`(view).getLocationInWindow(any()) + val pos = invocation.arguments[0] as IntArray + pos[0] = VIEW_LEFT + pos[1] = VIEW_TOP + null + } + .`when`(view) + .getLocationInWindow(any()) } @Test @@ -59,9 +64,8 @@ class ViewUtilTest : SysuiTestCase() { @Test fun touchIsWithinView_onTopLeftCorner_returnsTrue() { - assertThat(viewUtil.touchIsWithinView( - view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat()) - ).isTrue() + assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat())) + .isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java index ed07ad2a0976..207c35da1892 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/wakelock/WakeLockTest.java @@ -35,15 +35,18 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import java.util.List; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + + @SmallTest -@RunWith(Parameterized.class) +@RunWith(ParameterizedAndroidJunit4.class) public class WakeLockTest extends SysuiTestCase { - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") public static List<FlagsParameterization> getFlags() { return FlagsParameterization.allCombinationsOf( Flags.FLAG_DELAYED_WAKELOCK_RELEASE_ON_BACKGROUND_THREAD); @@ -114,4 +117,4 @@ public class WakeLockTest extends SysuiTestCase { // shouldn't throw an exception on production builds mWakeLock.release(WHY); } -}
\ No newline at end of file +} 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..7b0a55608970 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -27,6 +27,7 @@ import static android.service.notification.NotificationListenerService.REASON_GR import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING; +import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR; import static com.google.common.truth.Truth.assertThat; @@ -138,7 +139,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; @@ -467,7 +467,8 @@ public class BubblesTest extends SysuiTestCase { mock(UiEventLogger.class), mock(UserTracker.class), mock(AvalancheProvider.class), - mock(SystemSettings.class) + mock(SystemSettings.class), + mock(PackageManager.class) ); interruptionDecisionProvider.start(); @@ -2132,8 +2133,7 @@ public class BubblesTest extends SysuiTestCase { FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); mBubbleController.registerBubbleStateListener(bubbleStateListener); - mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), - new Rect(500, 1000, 600, 1100)); + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 1000); assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); @@ -2142,6 +2142,112 @@ public class BubblesTest extends SysuiTestCase { assertThat(mBubbleController.getLayerView().isExpanded()).isFalse(); } + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void dragBubbleBarBubble_selectedBubble_expandedViewCollapsesDuringDrag() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + // Add 2 bubbles + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + + // Select first bubble + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 0); + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + + // Drag first bubble, bubble should collapse + mBubbleController.startBubbleDrag(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isFalse(); + + // Stop dragging, first bubble should be expanded + mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT, 0); + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void dragBubbleBarBubble_unselectedBubble_expandedViewCollapsesDuringDrag() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + // Add 2 bubbles + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + + // Select first bubble + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 0); + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + + // Drag second bubble, bubble should collapse + mBubbleController.startBubbleDrag(mBubbleEntry2.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isFalse(); + + // Stop dragging, first bubble should be expanded + mBubbleController.stopBubbleDrag(BubbleBarLocation.LEFT, 0); + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void dismissBubbleBarBubble_selected_selectsAndExpandsNext() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + // Add 2 bubbles + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + + // Select first bubble + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 0); + // Drag first bubble to dismiss + mBubbleController.startBubbleDrag(mBubbleEntry.getKey()); + mBubbleController.dragBubbleToDismiss(mBubbleEntry.getKey()); + // Second bubble is selected and expanded + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry2.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + } + + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void dismissBubbleBarBubble_unselected_selectionDoesNotChange() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + // Add 2 bubbles + mEntryListener.onEntryAdded(mRow); + mEntryListener.onEntryAdded(mRow2); + mBubbleController.updateBubble(mBubbleEntry); + mBubbleController.updateBubble(mBubbleEntry2); + + // Select first bubble + mBubbleController.expandStackAndSelectBubbleFromLauncher(mBubbleEntry.getKey(), 0); + // Drag second bubble to dismiss + mBubbleController.startBubbleDrag(mBubbleEntry2.getKey()); + mBubbleController.dragBubbleToDismiss(mBubbleEntry2.getKey()); + // First bubble remains selected and expanded + assertThat(mBubbleData.getSelectedBubbleKey()).isEqualTo(mBubbleEntry.getKey()); + assertThat(mBubbleController.getLayerView().isExpanded()).isTrue(); + } + @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) @Test public void doesNotRegisterSensitiveStateListener() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 9dcd94687668..8eef930cbb0c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -15,8 +15,6 @@ */ package com.android.systemui; -import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; - import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @@ -99,8 +97,22 @@ public abstract class SysuiTestCase { .setProvideMainThread(true) .build(); - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + @ClassRule + public static final SetFlagsRule.ClassRule mSetFlagsClassRule = + new SetFlagsRule.ClassRule( + android.app.Flags.class, + android.hardware.biometrics.Flags.class, + android.multiuser.Flags.class, + android.net.platform.flags.Flags.class, + android.os.Flags.class, + android.service.controls.flags.Flags.class, + com.android.internal.telephony.flags.Flags.class, + com.android.server.notification.Flags.class, + com.android.systemui.Flags.class); + + // TODO(b/339471826): Fix Robolectric to execute the @ClassRule correctly + @Rule public final SetFlagsRule mSetFlagsRule = + isRobolectricTest() ? new SetFlagsRule() : mSetFlagsClassRule.createSetFlagsRule(); @Rule(order = 10) public final SceneContainerRule mSceneContainerRule = new SceneContainerRule(); 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/accessibility/data/repository/FakeOneHandedModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt new file mode 100644 index 000000000000..ac135afaebff --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeOneHandedModeRepository.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.accessibility.data.repository + +import android.os.UserHandle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class FakeOneHandedModeRepository : OneHandedModeRepository { + private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>() + + override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> { + return getFlow(userHandle.identifier) + } + + override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean { + getFlow(userHandle.identifier).value = isEnabled + return true + } + + /** initializes the flow if already not */ + private fun getFlow(userId: Int): MutableStateFlow<Boolean> { + return userMap.getOrPut(userId) { MutableStateFlow(false) } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.kt new file mode 100644 index 000000000000..9ee200ab5532 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/OneHandedModeRepositoryKosmos.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.accessibility.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeOneHandedModeRepository by Kosmos.Fixture { FakeOneHandedModeRepository() } +val Kosmos.oneHandedModeRepository by Kosmos.Fixture { fakeOneHandedModeRepository } 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 2e2cf9a61a8c..280996719e02 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 @@ -19,24 +17,25 @@ class FakePromptRepository : PromptRepository { private val _userId = MutableStateFlow<Int?>(null) override val userId = _userId.asStateFlow() + private val _requestId = MutableStateFlow<Long?>(null) + override val requestId = _requestId.asStateFlow() + 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() override fun setPrompt( promptInfo: PromptInfo, userId: Int, + requestId: Long, gatekeeperChallenge: Long?, kind: PromptKind, opPackageName: String, @@ -44,6 +43,7 @@ class FakePromptRepository : PromptRepository { setPrompt( promptInfo, userId, + requestId, gatekeeperChallenge, kind, forceConfirmation = false, @@ -53,6 +53,7 @@ class FakePromptRepository : PromptRepository { fun setPrompt( promptInfo: PromptInfo, userId: Int, + requestId: Long, gatekeeperChallenge: Long?, kind: PromptKind, forceConfirmation: Boolean = false, @@ -60,31 +61,23 @@ class FakePromptRepository : PromptRepository { ) { _promptInfo.value = promptInfo _userId.value = userId + _requestId.value = requestId _challenge.value = gatekeeperChallenge - _kind.value = kind + _promptKind.value = kind _isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation _opPackageName.value = opPackageName } - override fun unsetPrompt() { + override fun unsetPrompt(requestId: Long) { _promptInfo.value = null _userId.value = null + _requestId.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 - _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/brightness/data/repository/FakeBrightnessPolicyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt index d3ceb15ca5a2..f5d02f328336 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeBrightnessPolicyRepository.kt @@ -38,4 +38,8 @@ class FakeBrightnessPolicyRepository : BrightnessPolicyRepository { ) ) } + + fun setBaseUserRestriction() { + _restrictionPolicy.value = PolicyRestriction.Restricted(RestrictedLockUtils.EnforcedAdmin()) + } } 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/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt index 9f5c6b8faa38..d958bae7ae2a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt @@ -23,6 +23,7 @@ class FakeCommunalRepository( ) : CommunalRepository { override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) { this.currentScene.value = toScene + this._transitionState.value = flowOf(ObservableTransitionState.Idle(toScene)) } private val defaultTransitionState = ObservableTransitionState.Idle(CommunalScenes.Default) 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/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 3dd382f6db3e..3fe6973d36df 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -23,14 +23,15 @@ import com.android.systemui.communal.data.repository.communalPrefsRepository import com.android.systemui.communal.data.repository.communalRepository import com.android.systemui.communal.data.repository.communalWidgetRepository import com.android.systemui.communal.widgets.EditWidgetsActivityStarter -import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.log.logcatLogBuffer import com.android.systemui.plugins.activityStarter import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -42,6 +43,7 @@ import com.android.systemui.util.mockito.mock val Kosmos.communalInteractor by Fixture { CommunalInteractor( applicationScope = applicationCoroutineScope, + bgDispatcher = testDispatcher, broadcastDispatcher = broadcastDispatcher, communalRepository = communalRepository, widgetRepository = communalWidgetRepository, @@ -49,13 +51,13 @@ val Kosmos.communalInteractor by Fixture { mediaRepository = communalMediaRepository, smartspaceRepository = smartspaceRepository, keyguardInteractor = keyguardInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, communalSettingsInteractor = communalSettingsInteractor, appWidgetHost = mock(), editWidgetsActivityStarter = editWidgetsActivityStarter, userTracker = userTracker, activityStarter = activityStarter, userManager = userManager, - dockManager = fakeDockManager, sceneInteractor = sceneInteractor, logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/FakeShortcutHelperStartActivity.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/FakeShortcutHelperStartActivity.kt new file mode 100644 index 000000000000..3190171b180d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/FakeShortcutHelperStartActivity.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.keyboard.shortcut + +import android.content.Intent + +class FakeShortcutHelperStartActivity : (Intent) -> Unit { + + val startIntents = mutableListOf<Intent>() + + override fun invoke(intent: Intent) { + startIntents += intent + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt new file mode 100644 index 000000000000..3401cc4be231 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.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.keyboard.shortcut + +import android.content.applicationContext +import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperRepository +import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperTestHelper +import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperInteractor +import com.android.systemui.keyboard.shortcut.ui.ShortcutHelperActivityStarter +import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel +import com.android.systemui.keyguard.data.repository.fakeCommandQueue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.model.sysUiState +import com.android.systemui.settings.displayTracker + +val Kosmos.shortcutHelperRepository by + Kosmos.Fixture { ShortcutHelperRepository(fakeCommandQueue, broadcastDispatcher) } + +val Kosmos.shortcutHelperTestHelper by + Kosmos.Fixture { + ShortcutHelperTestHelper( + shortcutHelperRepository, + applicationContext, + broadcastDispatcher, + fakeCommandQueue + ) + } + +val Kosmos.shortcutHelperInteractor by + Kosmos.Fixture { + ShortcutHelperInteractor(displayTracker, testScope, sysUiState, shortcutHelperRepository) + } + +val Kosmos.shortcutHelperViewModel by + Kosmos.Fixture { ShortcutHelperViewModel(testDispatcher, shortcutHelperInteractor) } + +val Kosmos.fakeShortcutHelperStartActivity by Kosmos.Fixture { FakeShortcutHelperStartActivity() } + +val Kosmos.shortcutHelperActivityStarter by + Kosmos.Fixture { + ShortcutHelperActivityStarter( + applicationContext, + applicationCoroutineScope, + shortcutHelperViewModel, + fakeShortcutHelperStartActivity, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt new file mode 100644 index 000000000000..772ce415a6e9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyboard.shortcut.data.repository + +import android.content.Context +import android.content.Intent +import com.android.systemui.broadcast.FakeBroadcastDispatcher +import com.android.systemui.keyguard.data.repository.FakeCommandQueue + +class ShortcutHelperTestHelper( + repo: ShortcutHelperRepository, + private val context: Context, + private val fakeBroadcastDispatcher: FakeBroadcastDispatcher, + private val fakeCommandQueue: FakeCommandQueue, +) { + + init { + repo.start() + } + + fun hideFromActivity() { + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS) + ) + } + + fun showFromActivity() { + fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS) + ) + } + + fun toggle(deviceId: Int) { + fakeCommandQueue.doForEachCallback { it.toggleKeyboardShortcutsMenu(deviceId) } + } + + fun hideForSystem() { + fakeCommandQueue.doForEachCallback { it.dismissKeyboardShortcutsMenu() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 1e305d67d40d..96a40494b11a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.keyguard.shared.model.DismissAction @@ -101,7 +102,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _isKeyguardGoingAway = MutableStateFlow(false) override val isKeyguardGoingAway: Flow<Boolean> = _isKeyguardGoingAway - private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE) + private val _biometricUnlockState = + MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null)) override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null) @@ -110,9 +112,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _faceSensorLocation = MutableStateFlow<Point?>(null) override val faceSensorLocation: Flow<Point?> = _faceSensorLocation - private val _biometricUnlockSource = MutableStateFlow<BiometricUnlockSource?>(null) - override val biometricUnlockSource: Flow<BiometricUnlockSource?> = _biometricUnlockSource - private val _isQuickSettingsVisible = MutableStateFlow(false) override val isQuickSettingsVisible: Flow<Boolean> = _isQuickSettingsVisible.asStateFlow() @@ -213,12 +212,19 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { _dozeAmount.value = dozeAmount } - override fun setBiometricUnlockState(value: BiometricUnlockModel) { - _biometricUnlockState.tryEmit(value) + override fun setBiometricUnlockState( + mode: BiometricUnlockMode, + source: BiometricUnlockSource? + ) { + _biometricUnlockState.tryEmit(BiometricUnlockModel(mode, source)) + } + + fun setBiometricUnlockState(mode: BiometricUnlockMode) { + setBiometricUnlockState(mode, BiometricUnlockSource.FINGERPRINT_SENSOR) } fun setBiometricUnlockSource(source: BiometricUnlockSource?) { - _biometricUnlockSource.tryEmit(source) + setBiometricUnlockState(BiometricUnlockMode.NONE, source) } fun setFaceSensorLocation(location: Point?) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt index a242368d1ee1..2fe7438bcc77 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt @@ -40,12 +40,21 @@ import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent -/** Fake implementation of [KeyguardTransitionRepository] */ +/** + * Fake implementation of [KeyguardTransitionRepository]. + * + * By default, will be seeded with a transition from OFF -> LOCKSCREEN, which is the most common + * case. If the lockscreen is disabled, or we're in setup wizard, the repository will initialize + * with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior. + */ @SysUISingleton -class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitionRepository { +class FakeKeyguardTransitionRepository( + private val initInLockscreen: Boolean = true, +) : KeyguardTransitionRepository { private val _transitions = MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST) override val transitions: SharedFlow<TransitionStep> = _transitions + @Inject constructor() : this(initInLockscreen = true) private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> = MutableStateFlow( @@ -59,8 +68,21 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow() init { - // Seed the fake repository with the same initial steps the actual repository uses. - KeyguardTransitionRepositoryImpl.initialTransitionSteps.forEach { _transitions.tryEmit(it) } + // Seed with a FINISHED transition in OFF, same as the real repository. + _transitions.tryEmit( + TransitionStep( + KeyguardState.OFF, + KeyguardState.OFF, + 1f, + TransitionState.FINISHED, + ) + ) + + if (initInLockscreen) { + tryEmitInitialStepsFromOff(KeyguardState.LOCKSCREEN) + } else { + tryEmitInitialStepsFromOff(KeyguardState.OFF) + } } /** @@ -223,6 +245,32 @@ class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitio return if (info.animator == null) UUID.randomUUID() else null } + override suspend fun emitInitialStepsFromOff(to: KeyguardState) { + tryEmitInitialStepsFromOff(to) + } + + private fun tryEmitInitialStepsFromOff(to: KeyguardState) { + _transitions.tryEmit( + TransitionStep( + KeyguardState.OFF, + to, + 0f, + TransitionState.STARTED, + ownerName = "KeyguardTransitionRepository(boot)", + ) + ) + + _transitions.tryEmit( + TransitionStep( + KeyguardState.OFF, + to, + 1f, + TransitionState.FINISHED, + ownerName = "KeyguardTransitionRepository(boot)", + ), + ) + } + override fun updateTransition( transitionId: UUID, @FloatRange(from = 0.0, to = 1.0) value: Float, 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/KeyguardDismissActionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt index 2c6d44f10152..03e5a90c9f7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorKosmos.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.domain.interactor.sceneInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi @@ -29,5 +30,6 @@ val Kosmos.keyguardDismissActionInteractor by transitionInteractor = keyguardTransitionInteractor, dismissInteractor = keyguardDismissInteractor, applicationScope = testScope.backgroundScope, + sceneInteractor = sceneInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt index 2e751cce7bad..02842cc3f56b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorFactory.kt @@ -53,6 +53,7 @@ object KeyguardInteractorFactory { shadeRepository: FakeShadeRepository = FakeShadeRepository(), sceneInteractor: SceneInteractor = mock(), fromGoneTransitionInteractor: FromGoneTransitionInteractor = mock(), + fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor = mock(), sharedNotificationContainerInteractor: SharedNotificationContainerInteractor? = null, powerInteractor: PowerInteractor = PowerInteractorFactory.create().powerInteractor, testScope: CoroutineScope = TestScope(), @@ -98,6 +99,7 @@ object KeyguardInteractorFactory { keyguardTransitionInteractor = keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, + fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor }, sharedNotificationContainerInteractor = { sncInteractor }, applicationScope = testScope, ), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt index 94267189e179..81d8f0b4ca53 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorKosmos.kt @@ -18,20 +18,20 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.common.ui.domain.interactor.configurationInteractor +import com.android.systemui.keyguard.data.repository.fakeCommandQueue import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.data.repository.shadeRepository -import com.android.systemui.statusbar.commandQueue import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor val Kosmos.keyguardInteractor: KeyguardInteractor by Kosmos.Fixture { KeyguardInteractor( repository = keyguardRepository, - commandQueue = commandQueue, + commandQueue = fakeCommandQueue, powerInteractor = powerInteractor, bouncerRepository = keyguardBouncerRepository, configurationInteractor = configurationInteractor, @@ -39,6 +39,7 @@ val Kosmos.keyguardInteractor: KeyguardInteractor by keyguardTransitionInteractor = keyguardTransitionInteractor, sceneInteractorProvider = { sceneInteractor }, fromGoneTransitionInteractor = { fromGoneTransitionInteractor }, + fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor }, sharedNotificationContainerInteractor = { sharedNotificationContainerInteractor }, applicationScope = testScope.backgroundScope, ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt new file mode 100644 index 000000000000..7d8d33f7dd11 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.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.keyguard.domain.interactor + +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor + +val Kosmos.keyguardTransitionBootInteractor: KeyguardTransitionBootInteractor by + Kosmos.Fixture { + KeyguardTransitionBootInteractor( + scope = applicationCoroutineScope, + deviceEntryInteractor = deviceEntryInteractor, + deviceProvisioningInteractor = deviceProvisioningInteractor, + keyguardTransitionInteractor = keyguardTransitionInteractor, + repository = keyguardTransitionRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt index 6cc1e8eba73d..c90642d93939 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneInteractor val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by Kosmos.Fixture { @@ -32,5 +33,6 @@ val Kosmos.keyguardTransitionInteractor: KeyguardTransitionInteractor by fromAodTransitionInteractor = { fromAodTransitionInteractor }, fromAlternateBouncerTransitionInteractor = { fromAlternateBouncerTransitionInteractor }, fromDozingTransitionInteractor = { fromDozingTransitionInteractor }, + sceneInteractor = { sceneInteractor } ) } 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..bd9c0be6b0b7 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 @@ -16,7 +16,9 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor 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 +31,7 @@ val Kosmos.windowManagerLockscreenVisibilityInteractor by fromBouncerInteractor = fromPrimaryBouncerTransitionInteractor, fromAlternateBouncerInteractor = fromAlternateBouncerTransitionInteractor, notificationLaunchAnimationInteractor = notificationLaunchAnimationInteractor, + sceneInteractor = { sceneInteractor }, + deviceEntryInteractor = { deviceEntryInteractor }, ) } 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/AodToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt index 460913f75eb4..b8fcec648393 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodToLockscreenTransitionViewModelKosmos.kt @@ -18,7 +18,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture @@ -27,7 +26,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.aodToLockscreenTransitionViewModel by Fixture { AodToLockscreenTransitionViewModel( - deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, shadeInteractor = shadeInteractor, animationFlow = keyguardTransitionAnimationFlow, ) 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/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt index a81ac038b38a..0e95320bdf4c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt @@ -55,6 +55,7 @@ import com.android.systemui.settings.brightness.domain.interactor.brightnessMirr import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shade.shadeController +import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository @@ -78,6 +79,7 @@ class KosmosJavaAdapter( val configurationInteractor by lazy { kosmos.configurationInteractor } val bouncerRepository by lazy { kosmos.bouncerRepository } val communalRepository by lazy { kosmos.fakeCommunalRepository } + val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor } val keyguardRepository by lazy { kosmos.fakeKeyguardRepository } val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository } val keyguardInteractor by lazy { kosmos.keyguardInteractor } 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/qrcodescanner/QRCodeScannerControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.kt new file mode 100644 index 000000000000..8ad6087cfe82 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qrcodescanner/QRCodeScannerControllerKosmos.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.qrcodescanner + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qrcodescanner.controller.QRCodeScannerController +import com.android.systemui.util.mockito.mock + +val Kosmos.qrCodeScannerController by Kosmos.Fixture { mock<QRCodeScannerController>() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt index 7e0e7d1f46e8..093ebd6c6b63 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSTile.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package com.android.systemui.qs.pipeline.domain.interactor +package com.android.systemui.qs -import android.view.View import com.android.internal.logging.InstanceId +import com.android.systemui.animation.Expandable import com.android.systemui.plugins.qs.QSTile class FakeQSTile( @@ -56,11 +56,11 @@ class FakeQSTile( callbacks.clear() } - override fun click(view: View?) {} + override fun click(expandable: Expandable?) {} - override fun secondaryClick(view: View?) {} + override fun secondaryClick(expandable: Expandable?) {} - override fun longClick(view: View?) {} + override fun longClick(expandable: Expandable?) {} override fun userSwitch(currentUser: Int) { user = currentUser 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/IconAndNameCustomRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryKosmos.kt new file mode 100644 index 000000000000..d686699f795b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconAndNameCustomRepositoryKosmos.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.data.repository + +import android.content.packageManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext +import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository +import com.android.systemui.settings.userTracker +import com.android.systemui.util.mockito.whenever + +val Kosmos.iconAndNameCustomRepository by + Kosmos.Fixture { + whenever(userTracker.userContext.packageManager).thenReturn(packageManager) + IconAndNameCustomRepository( + installedTilesRepository, + userTracker, + backgroundCoroutineContext, + ) + } 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/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.kt new file mode 100644 index 000000000000..d8af3fa98d7b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/InfiniteGridSizeRepositoryKosmos.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.data.repository + +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/data/repository/StockTilesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryKosmos.kt new file mode 100644 index 000000000000..ff33650d9f02 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/StockTilesRepositoryKosmos.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.qs.panels.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testCase + +var Kosmos.stockTilesRepository by + Kosmos.Fixture { + testCase.context.orCreateTestableResources + StockTilesRepository(testCase.context.resources) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorKosmos.kt new file mode 100644 index 000000000000..bd54fd471807 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/EditTilesListInteractorKosmos.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.qs.panels.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.data.repository.iconAndNameCustomRepository +import com.android.systemui.qs.panels.data.repository.stockTilesRepository +import com.android.systemui.qs.tiles.viewmodel.qSTileConfigProvider + +val Kosmos.editTilesListInteractor by + Kosmos.Fixture { + EditTilesListInteractor( + stockTilesRepository, + qSTileConfigProvider, + iconAndNameCustomRepository, + ) + } 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 e44fa0717602..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 @@ -18,6 +18,15 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.qs.panels.data.repository.gridLayoutTypeRepository +import com.android.systemui.qs.panels.shared.model.GridLayoutType +import com.android.systemui.qs.panels.shared.model.InfiniteGridLayoutType +import com.android.systemui.qs.panels.ui.compose.GridLayout val Kosmos.gridLayoutTypeInteractor by Kosmos.Fixture { GridLayoutTypeInteractor(gridLayoutTypeRepository) } + +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 new file mode 100644 index 000000000000..34b266a54f41 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridLayoutKosmos.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.ui.compose.InfiniteGridLayout + +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/EditModeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt new file mode 100644 index 000000000000..612a5d9917ff --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModelKosmos.kt @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.qs.panels.domain.interactor.editTilesListInteractor +import com.android.systemui.qs.panels.domain.interactor.gridLayoutMap +import com.android.systemui.qs.panels.domain.interactor.gridLayoutTypeInteractor +import com.android.systemui.qs.panels.domain.interactor.infiniteGridLayout +import com.android.systemui.qs.pipeline.domain.interactor.currentTilesInteractor +import com.android.systemui.qs.pipeline.domain.interactor.minimumTilesInteractor + +val Kosmos.editModeViewModel by + Kosmos.Fixture { + EditModeViewModel( + editTilesListInteractor, + currentTilesInteractor, + minimumTilesInteractor, + infiniteGridLayout, + applicationCoroutineScope, + gridLayoutTypeInteractor, + gridLayoutMap, + ) + } 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 9851f043e39a..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 @@ -17,18 +17,19 @@ package com.android.systemui.qs.panels.ui.viewmodel 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.shared.model.InfiniteGridLayoutType -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 Kosmos.Fixture { TileGridViewModel( gridLayoutTypeInteractor, - mapOf(Pair(InfiniteGridLayoutType::class.java, InfiniteGridLayout())), + gridLayoutMap, currentTilesInteractor, - iconTilesInteractor + infiniteGridLayout, + applicationCoroutineScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt index ff6b7d083df7..ed4c67ee3511 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeInstalledTilesComponentRepository.kt @@ -17,23 +17,78 @@ package com.android.systemui.qs.pipeline.data.repository import android.content.ComponentName +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.pm.ServiceInfo +import android.graphics.drawable.Drawable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map class FakeInstalledTilesComponentRepository : InstalledTilesComponentRepository { - private val installedComponentsPerUser = - mutableMapOf<Int, MutableStateFlow<Set<ComponentName>>>() + private val installedServicesPerUser = mutableMapOf<Int, MutableStateFlow<List<ServiceInfo>>>() override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> { - return getFlow(userId).asStateFlow() + return getFlow(userId).map { it.map { it.componentName }.toSet() } + } + + override fun getInstalledTilesServiceInfos(userId: Int): List<ServiceInfo> { + return getFlow(userId).value } fun setInstalledPackagesForUser(userId: Int, components: Set<ComponentName>) { - getFlow(userId).value = components + getFlow(userId).value = + components.map { + ServiceInfo().apply { + packageName = it.packageName + name = it.className + applicationInfo = ApplicationInfo() + } + } + } + + fun setInstalledServicesForUser(userId: Int, services: List<ServiceInfo>) { + getFlow(userId).value = services.toList() } - private fun getFlow(userId: Int): MutableStateFlow<Set<ComponentName>> = - installedComponentsPerUser.getOrPut(userId) { MutableStateFlow(emptySet()) } + private fun getFlow(userId: Int): MutableStateFlow<List<ServiceInfo>> = + installedServicesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) } + + companion object { + fun ServiceInfo( + componentName: ComponentName, + serviceName: String, + serviceIcon: Drawable? = null, + appName: String = "", + appIcon: Drawable? = null + ): ServiceInfo { + val appInfo = + object : ApplicationInfo() { + override fun loadLabel(pm: PackageManager): CharSequence { + return appName + } + + override fun loadIcon(pm: PackageManager?): Drawable? { + return appIcon + } + } + val serviceInfo = + object : ServiceInfo() { + override fun loadLabel(pm: PackageManager): CharSequence { + return serviceName + } + + override fun loadIcon(pm: PackageManager?): Drawable? { + return serviceIcon ?: getApplicationInfo().loadIcon(pm) + } + } + .apply { + packageName = componentName.packageName + name = componentName.className + applicationInfo = appInfo + } + return serviceInfo + } + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractorKosmos.kt new file mode 100644 index 000000000000..ef1189f25cff --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/MinimumTilesInteractorKosmos.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.pipeline.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.pipeline.data.repository.minimumTilesRepository + +var Kosmos.minimumTilesInteractor by + Kosmos.Fixture { MinimumTilesInteractor(minimumTilesRepository) } 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/base/actions/FakeQSTileIntentUserInputHandler.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt index 0307c414351f..f50443ec4e86 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/FakeQSTileIntentUserInputHandler.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.tiles.base.actions import android.app.PendingIntent import android.content.Intent -import android.view.View +import com.android.systemui.animation.Expandable /** * Fake implementation of [QSTileIntentUserInputHandler] interface. Consider using this alongside @@ -31,22 +31,28 @@ class FakeQSTileIntentUserInputHandler : QSTileIntentUserInputHandler { private val mutableInputs = mutableListOf<Input>() - override fun handle(view: View?, intent: Intent) { - mutableInputs.add(Input.Intent(view, intent)) + override fun handle( + expandable: Expandable?, + intent: Intent, + handleDismissShadeShowOverLockScreenWhenLocked: Boolean + ) { + mutableInputs.add(Input.Intent(expandable, intent)) } override fun handle( - view: View?, + expandable: Expandable?, pendingIntent: PendingIntent, requestLaunchingDefaultActivity: Boolean ) { - mutableInputs.add(Input.PendingIntent(view, pendingIntent, requestLaunchingDefaultActivity)) + mutableInputs.add( + Input.PendingIntent(expandable, pendingIntent, requestLaunchingDefaultActivity) + ) } sealed interface Input { - data class Intent(val view: View?, val intent: android.content.Intent) : Input + data class Intent(val expandable: Expandable?, val intent: android.content.Intent) : Input data class PendingIntent( - val view: View?, + val expandable: Expandable?, val pendingIntent: android.app.PendingIntent, val requestLaunchingDefaultActivity: Boolean ) : Input diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.kt new file mode 100644 index 000000000000..ccfb6092a2e3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandlerKosmos.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.tiles.base.actions + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.qsTileIntentUserInputHandler by Kosmos.Fixture { FakeQSTileIntentUserInputHandler() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.kt new file mode 100644 index 000000000000..146c1ad6ab70 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/analytics/QSTileAnalyticsKosmos.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.tiles.base.analytics + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.qsTileAnalytics by Kosmos.Fixture { mock<QSTileAnalytics>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.kt new file mode 100644 index 000000000000..9ad49f052c9e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorKosmos.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.tiles.base.interactor + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeDisabledByPolicyInteractor by Kosmos.Fixture { FakeDisabledByPolicyInteractor() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/QSTileInputTestKtx.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/QSTileInputTestKtx.kt index 832b07a0763b..9cb76bb412f9 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/QSTileInputTestKtx.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/QSTileInputTestKtx.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.tiles.base.interactor import android.os.UserHandle -import android.view.View +import com.android.systemui.animation.Expandable import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction object QSTileInputTestKtx { @@ -25,12 +25,12 @@ object QSTileInputTestKtx { fun <T> click( data: T, user: UserHandle = UserHandle.CURRENT, - view: View? = null, - ): QSTileInput<T> = QSTileInput(user, QSTileUserAction.Click(view), data) + expandable: Expandable? = null, + ): QSTileInput<T> = QSTileInput(user, QSTileUserAction.Click(expandable), data) fun <T> longClick( data: T, user: UserHandle = UserHandle.CURRENT, - view: View? = null, - ): QSTileInput<T> = QSTileInput(user, QSTileUserAction.LongClick(view), data) + expandable: Expandable? = null, + ): QSTileInput<T> = QSTileInput(user, QSTileUserAction.LongClick(expandable), data) } 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/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.kt new file mode 100644 index 000000000000..d9c0361c8dc9 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/onehanded/OneHandedModeTileKosmos.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.onehanded + +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger + +val Kosmos.qsOneHandedModeTileConfig by + Kosmos.Fixture { QSAccessibilityModule.provideOneHandedTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt new file mode 100644 index 000000000000..dcfcce77942e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/qr/QRCodeScannerTileKosmos.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.tiles.impl.qr + +import android.content.res.mainResources +import com.android.systemui.classifier.fakeFalsingManager +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.backgroundCoroutineContext +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.kosmos.testScope +import com.android.systemui.qrcodescanner.dagger.QRCodeScannerModule +import com.android.systemui.qrcodescanner.qrCodeScannerController +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.qs.tiles.base.actions.qsTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.analytics.qsTileAnalytics +import com.android.systemui.qs.tiles.base.interactor.fakeDisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl +import com.android.systemui.qs.tiles.impl.custom.qsTileLogger +import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileDataInteractor +import com.android.systemui.qs.tiles.impl.qr.domain.interactor.QRCodeScannerTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.qr.ui.QRCodeScannerTileMapper +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.time.systemClock + +val Kosmos.qsQRCodeScannerTileConfig by + Kosmos.Fixture { QRCodeScannerModule.provideQRCodeScannerTileConfig(qsEventLogger) } + +val Kosmos.qrCodeScannerTileDataInteractor by + Kosmos.Fixture { + QRCodeScannerTileDataInteractor( + backgroundCoroutineContext, + applicationCoroutineScope, + qrCodeScannerController + ) + } + +val Kosmos.qrCodeScannerTileUserActionInteractor by + Kosmos.Fixture { QRCodeScannerTileUserActionInteractor(qsTileIntentUserInputHandler) } + +val Kosmos.qrCodeScannerTileMapper by + Kosmos.Fixture { QRCodeScannerTileMapper(mainResources, mainResources.newTheme()) } + +val Kosmos.qsQRCodeScannerViewModel by + Kosmos.Fixture { + QSTileViewModelImpl( + qsQRCodeScannerTileConfig, + { qrCodeScannerTileUserActionInteractor }, + { qrCodeScannerTileDataInteractor }, + { qrCodeScannerTileMapper }, + fakeDisabledByPolicyInteractor, + fakeUserRepository, + fakeFalsingManager, + qsTileAnalytics, + qsTileLogger, + systemClock, + testDispatcher, + testScope.backgroundScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt index a654d6fc239a..e0f60e9b3a01 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/adapter/FakeQSSceneAdapter.kt @@ -21,7 +21,6 @@ import android.view.View import com.android.systemui.settings.brightness.MirrorController import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull @@ -30,8 +29,16 @@ class FakeQSSceneAdapter( override val qqsHeight: Int = 0, override val qsHeight: Int = 0, ) : QSSceneAdapter { + private val _customizerState = MutableStateFlow<CustomizerState>(CustomizerState.Hidden) + + private val _customizerShowing = MutableStateFlow(false) + override val isCustomizerShowing = _customizerShowing.asStateFlow() + private val _customizing = MutableStateFlow(false) - override val isCustomizing: StateFlow<Boolean> = _customizing.asStateFlow() + override val isCustomizing = _customizing.asStateFlow() + + private val _animationDuration = MutableStateFlow(0) + override val customizerAnimationDuration = _animationDuration.asStateFlow() private val _view = MutableStateFlow<View?>(null) override val qsView: Flow<View> = _view.filterNotNull() @@ -58,7 +65,7 @@ class FakeQSSceneAdapter( } fun setCustomizing(value: Boolean) { - _customizing.value = value + updateCustomizerFlows(if (value) CustomizerState.Showing else CustomizerState.Hidden) } override suspend fun applyBottomNavBarPadding(padding: Int) { @@ -66,10 +73,18 @@ class FakeQSSceneAdapter( } override fun requestCloseCustomizer() { - _customizing.value = false + updateCustomizerFlows(CustomizerState.Hidden) } override fun setBrightnessMirrorController(mirrorController: MirrorController?) { brightnessMirrorController = mirrorController } + + private fun updateCustomizerFlows(customizerState: CustomizerState) { + _customizerState.value = customizerState + _customizing.value = customizerState.isCustomizing + _customizerShowing.value = customizerState.isShowing + _animationDuration.value = + (customizerState as? CustomizerState.Animating)?.animationDuration?.toInt() ?: 0 + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt new file mode 100644 index 000000000000..641a75716ea0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryUtil.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.data.repository + +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.shared.model.Scenes +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent + +private val mutableTransitionState = + MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(Scenes.Lockscreen)) + +fun Kosmos.setSceneTransition( + transition: ObservableTransitionState, + scope: TestScope = testScope, + repository: SceneContainerRepository = sceneContainerRepository +) { + repository.setTransitionState(mutableTransitionState) + mutableTransitionState.value = transition + scope.runCurrent() +} + +fun Transition( + from: SceneKey, + to: SceneKey, + currentScene: Flow<SceneKey> = flowOf(to), + progress: Flow<Float> = flowOf(0f), + isInitiatedByUserInput: Boolean = false, + isUserInputOngoing: Flow<Boolean> = flowOf(false), +): ObservableTransitionState.Transition { + return ObservableTransitionState.Transition( + fromScene = from, + toScene = to, + currentScene = currentScene, + progress = progress, + isInitiatedByUserInput = isInitiatedByUserInput, + isUserInputOngoing = isUserInputOngoing + ) +} + +fun Idle(currentScene: SceneKey): ObservableTransitionState.Idle { + return ObservableTransitionState.Idle(currentScene) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt index ebe591b32536..d82286fd3569 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneContainerStartableKosmos.kt @@ -32,6 +32,7 @@ import com.android.systemui.kosmos.testScope import com.android.systemui.model.sysUiState import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable +import com.android.systemui.scene.session.shared.shadeSessionStorage import com.android.systemui.scene.shared.logger.sceneLogger import com.android.systemui.settings.displayTracker import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -65,5 +66,6 @@ val Kosmos.sceneContainerStartable by Fixture { shadeInteractor = shadeInteractor, uiEventLogger = uiEventLogger, sceneBackInteractor = sceneBackInteractor, + shadeSessionStorage = shadeSessionStorage, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/session/shared/SessionStorageKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/session/shared/SessionStorageKosmos.kt new file mode 100644 index 000000000000..0bee9378b7c4 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/session/shared/SessionStorageKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.scene.session.shared + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +var Kosmos.shadeSessionStorage by Fixture { SessionStorage() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt index 59a01cbedc5c..957a60f83134 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/shared/model/FakeSceneDataSource.kt @@ -42,6 +42,10 @@ class FakeSceneDataSource( } } + override fun snapToScene(toScene: SceneKey) { + changeScene(toScene) + } + /** * Pauses scene changes. * 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 0101961c9b72..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 @@ -23,6 +23,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.FakeShadeRepository import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import org.junit.Assert @@ -58,11 +59,33 @@ class ShadeTestUtil constructor(val delegate: ShadeTestUtilDelegate) { delegate.setLockscreenShadeExpansion(lockscreenShadeExpansion) } - /** Sets whether the user is moving the shade with touch input. */ + /** Sets whether the user is moving the shade with touch input on Lockscreen. */ fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) { delegate.assertFlagValid() delegate.setLockscreenShadeTracking(lockscreenShadeTracking) } + + /** Sets whether the user is moving the shade with touch input. */ + fun setTracking(tracking: Boolean) { + delegate.assertFlagValid() + delegate.setTracking(tracking) + } + + /** Sets the shade to half collapsed with no touch input. */ + fun programmaticCollapseShade() { + 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. */ @@ -79,11 +102,21 @@ interface ShadeTestUtilDelegate { /** Sets whether the user is moving the shade with touch input. */ fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) + /** Sets whether the user is moving the shade with touch input. */ + fun setTracking(tracking: Boolean) + /** Sets shade expansion to a value between 0-1. */ fun setShadeExpansion(shadeExpansion: Float) /** Sets QS expansion to a value between 0-1. */ fun setQsExpansion(qsExpansion: Float) + + /** 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. */ @@ -103,6 +136,10 @@ class ShadeTestUtilLegacyImpl(val testScope: TestScope, val shadeRepository: Fak shadeRepository.setLegacyLockscreenShadeTracking(lockscreenShadeTracking) } + override fun setTracking(tracking: Boolean) { + shadeRepository.setLegacyShadeTracking(tracking) + } + override fun assertFlagValid() { Assert.assertFalse(SceneContainerFlag.isEnabled) } @@ -118,6 +155,19 @@ class ShadeTestUtilLegacyImpl(val testScope: TestScope, val shadeRepository: Fak shadeRepository.setQsExpansion(qsExpansion) testScope.runCurrent() } + + override fun programmaticCollapseShade() { + 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. */ @@ -126,14 +176,16 @@ class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: Scen val isUserInputOngoing = MutableStateFlow(true) override fun setShadeAndQsExpansion(shadeExpansion: Float, qsExpansion: Float) { - if (shadeExpansion == 0f) { - setTransitionProgress(Scenes.Lockscreen, Scenes.QuickSettings, qsExpansion) - } else if (qsExpansion == 0f) { - setTransitionProgress(Scenes.Lockscreen, Scenes.Shade, shadeExpansion) - } else if (shadeExpansion == 1f) { + if (shadeExpansion == 1f) { setIdleScene(Scenes.Shade) } else if (qsExpansion == 1f) { setIdleScene(Scenes.QuickSettings) + } else if (shadeExpansion == 0f && qsExpansion == 0f) { + setIdleScene(Scenes.Lockscreen) + } else if (shadeExpansion == 0f) { + setTransitionProgress(Scenes.Lockscreen, Scenes.QuickSettings, qsExpansion) + } else if (qsExpansion == 0f) { + setTransitionProgress(Scenes.Lockscreen, Scenes.Shade, shadeExpansion) } else { setTransitionProgress(Scenes.Shade, Scenes.QuickSettings, qsExpansion) } @@ -149,6 +201,20 @@ class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: Scen setShadeAndQsExpansion(0f, qsExpansion) } + override fun programmaticCollapseShade() { + 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) @@ -160,7 +226,11 @@ class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: Scen } override fun setLockscreenShadeTracking(lockscreenShadeTracking: Boolean) { - isUserInputOngoing.value = lockscreenShadeTracking + setTracking(lockscreenShadeTracking) + } + + override fun setTracking(tracking: Boolean) { + isUserInputOngoing.value = tracking } private fun setIdleScene(scene: SceneKey) { @@ -171,15 +241,21 @@ class ShadeTestUtilSceneImpl(val testScope: TestScope, val sceneInteractor: Scen testScope.runCurrent() } - private fun setTransitionProgress(from: SceneKey, to: SceneKey, progress: Float) { + private fun setTransitionProgress( + from: SceneKey, + to: SceneKey, + progress: Float, + isInitiatedByUserInput: Boolean = true + ) { sceneInteractor.changeScene(from, "test") val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Transition( fromScene = from, toScene = to, + currentScene = flowOf(to), progress = MutableStateFlow(progress), - isInitiatedByUserInput = true, + isInitiatedByUserInput = isInitiatedByUserInput, isUserInputOngoing = isUserInputOngoing, ) ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.kt new file mode 100644 index 000000000000..872eba06961e --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeSceneViewModelKosmos.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.shade.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeSceneViewModel + +val Kosmos.notificationsShadeSceneViewModel: NotificationsShadeSceneViewModel by + Kosmos.Fixture { + NotificationsShadeSceneViewModel( + applicationScope = applicationCoroutineScope, + overlayShadeViewModel = overlayShadeViewModel, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.kt new file mode 100644 index 000000000000..45ec03241495 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModelKosmos.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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.shade.ui.viewmodel + +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneInteractor +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.overlayShadeViewModel: OverlayShadeViewModel by + Kosmos.Fixture { + OverlayShadeViewModel( + applicationScope = applicationCoroutineScope, + sceneInteractor = sceneInteractor, + deviceEntryInteractor = deviceEntryInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt new file mode 100644 index 000000000000..8c5ff1d5d216 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.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.shade.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel + +val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by + Kosmos.Fixture { + QuickSettingsShadeSceneViewModel( + applicationScope = applicationCoroutineScope, + overlayShadeViewModel = overlayShadeViewModel, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java index 2bd584eac5e3..562ac0c15a0b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotificationEntryBuilder.java @@ -27,6 +27,8 @@ import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; +import androidx.annotation.NonNull; + import com.android.internal.logging.InstanceId; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SbnBuilder; @@ -36,6 +38,7 @@ import com.android.systemui.util.time.FakeSystemClock; import kotlin.Unit; import java.util.ArrayList; +import java.util.function.Consumer; /** * Combined builder for constructing a NotificationEntry and its associated StatusBarNotification @@ -73,6 +76,20 @@ public class NotificationEntryBuilder { mCreationTime = source.getCreationTime(); } + /** Allows the caller to sub-build the ranking */ + @NonNull + public NotificationEntryBuilder updateRanking(@NonNull Consumer<RankingBuilder> rankingUpdater) { + rankingUpdater.accept(mRankingBuilder); + return this; + } + + /** Allows the caller to sub-build the SBN */ + @NonNull + public NotificationEntryBuilder updateSbn(@NonNull Consumer<SbnBuilder> sbnUpdater) { + sbnUpdater.accept(mSbnBuilder); + return this; + } + /** Update an the parent on an existing entry */ public static void setNewParent(NotificationEntry entry, GroupEntry parent) { entry.setParent(parent); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt index 2e983a820240..980d65fde6de 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/repository/FakeHeadsUpRowRepository.kt @@ -18,7 +18,11 @@ package com.android.systemui.statusbar.notification.data.repository import kotlinx.coroutines.flow.MutableStateFlow -class FakeHeadsUpRowRepository(override val key: String, override val elementKey: Any) : +class FakeHeadsUpRowRepository(override val key: String, override val elementKey: Any = Any()) : HeadsUpRowRepository { + constructor(key: String, isPinned: Boolean) : this(key = key) { + this.isPinned.value = isPinned + } + override val isPinned: MutableStateFlow<Boolean> = MutableStateFlow(false) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt index d3451075c51b..c74aec1fc59c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/HeadsUpNotificationInteractorKosmos.kt @@ -16,11 +16,20 @@ package com.android.systemui.statusbar.notification.stack.domain.interactor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository val Kosmos.headsUpNotificationInteractor by Fixture { - HeadsUpNotificationInteractor(headsUpNotificationRepository) + HeadsUpNotificationInteractor( + headsUpNotificationRepository, + deviceEntryFaceAuthInteractor, + keyguardTransitionInteractor, + notificationsKeyguardInteractor, + shadeInteractor, + ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt index 94f6ecd36c7c..de8b3500a88a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import com.android.systemui.dump.dumpManager -import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher @@ -42,7 +41,6 @@ val Kosmos.notificationListViewModel by Fixture { activeNotificationsInteractor, notificationStackInteractor, headsUpNotificationInteractor, - keyguardInteractor, remoteInputInteractor, seenNotificationsInteractor, shadeInteractor, 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/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt index cce038f4ffc1..8229575a128f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt @@ -23,6 +23,7 @@ import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.statusbar.pipeline.mobile.data.model.ServiceStateModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy @@ -93,6 +94,8 @@ class FakeMobileConnectionsRepository( private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON) override val defaultMobileIconGroup = _defaultMobileIconGroup + override val deviceServiceState = MutableStateFlow<ServiceStateModel?>(null) + override val isAnySimSecure = MutableStateFlow(false) override fun getIsAnySimSecure(): Boolean = isAnySimSecure.value diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index de6c87c2b515..3a4bf8e48d33 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -81,6 +81,8 @@ class FakeMobileIconsInteractor( override val isForceHidden = MutableStateFlow(false) + override val isDeviceInEmergencyCallsOnlyMode = MutableStateFlow(false) + /** Always returns a new fake interactor */ override fun getMobileConnectionInteractorForSubId(subId: Int): FakeMobileIconInteractor { return FakeMobileIconInteractor(tableLogBuffer).also { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 3e9ae4d2e354..1f2ecb7d172d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.yield class FakeUserRepository @Inject constructor() : UserRepository { companion object { // User id to represent a non system (human) user id. We presume this is the main user. - private const val MAIN_USER_ID = 10 + const val MAIN_USER_ID = 10 private const val DEFAULT_SELECTED_USER = 0 private val DEFAULT_SELECTED_USER_INFO = @@ -84,6 +84,10 @@ class FakeUserRepository @Inject constructor() : UserRepository { override var isRefreshUsersPaused: Boolean = false + override suspend fun getMainUserId(): Int? { + return MAIN_USER_ID + } + var refreshUsersCallCount: Int = 0 private set 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/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt index efccafc95a11..295e150e00d8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt @@ -22,6 +22,7 @@ package com.android.systemui.util.mockito * be null"). To fix this, we can use methods that modify the return type to be nullable. This * causes Kotlin to skip the null checks. */ +import kotlin.DeprecationLevel.WARNING import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatcher import org.mockito.MockSettings @@ -37,6 +38,11 @@ import org.mockito.stubbing.Stubber * * Generic T is nullable because implicitly bounded by Any?. */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "eq", imports = ["org.mockito.kotlin.eq"]), + level = WARNING +) fun <T> eq(obj: T): T = Mockito.eq<T>(obj) ?: obj /** @@ -45,8 +51,18 @@ fun <T> eq(obj: T): T = Mockito.eq<T>(obj) ?: obj * * Generic T is nullable because implicitly bounded by Any?. */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "any(type)", imports = ["org.mockito.kotlin.any"]), + level = WARNING +) fun <T> any(type: Class<T>): T = Mockito.any<T>(type) +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "any()", imports = ["org.mockito.kotlin.any"]), + level = WARNING +) inline fun <reified T> any(): T = any(T::class.java) /** @@ -55,9 +71,23 @@ inline fun <reified T> any(): T = any(T::class.java) * * Generic T is nullable because implicitly bounded by Any?. */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "argThat(matcher)", imports = ["org.mockito.kotlin.argThat"]), + level = WARNING +) fun <T> argThat(matcher: ArgumentMatcher<T>): T = Mockito.argThat(matcher) -/** Kotlin type-inferred version of Mockito.nullable() */ +/** + * Kotlin type-inferred version of Mockito.nullable() + * + * @see org.mockito.kotlin.anyOrNull + */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "anyOrNull()", imports = ["org.mockito.kotlin.anyOrNull"]), + level = WARNING +) inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java) /** @@ -65,14 +95,28 @@ inline fun <reified T> nullable(): T? = Mockito.nullable(T::class.java) * null is returned. * * Generic T is nullable because implicitly bounded by Any?. + * + * @see org.mockito.kotlin.capture */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "capture(argumentCaptor)", imports = ["org.mockito.kotlin.capture"]), + level = WARNING +) fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture() /** * Helper function for creating an argumentCaptor in kotlin. * * Generic T is nullable because implicitly bounded by Any?. + * + * @see org.mockito.kotlin.argumentCaptor */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "argumentCaptor()", imports = ["org.mockito.kotlin.argumentCaptor"]), + level = WARNING +) inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> = ArgumentCaptor.forClass(T::class.java) @@ -81,18 +125,55 @@ inline fun <reified T : Any> argumentCaptor(): ArgumentCaptor<T> = * * Generic T is nullable because implicitly bounded by Any?. * + * Updated kotlin-mockito usage: + * ``` + * val value: Widget = mock<> { + * on { status } doReturn "OK" + * on { buttonPress } doNothing + * on { destroy } doAnswer error("Boom!") + * } + * ``` + * + * __Deprecation note__ + * + * Automatic replacement is not possible due to a change in lambda receiver type to KStubbing<T> + * * @param apply builder function to simplify stub configuration by improving type inference. + * @see org.mockito.kotlin.mock + * @see org.mockito.kotlin.KStubbing.on */ +@Suppress("DeprecatedCallableAddReplaceWith") +@Deprecated("Replace with mockito-kotlin. See http://go/mockito-kotlin", level = WARNING) inline fun <reified T : Any> mock(settings: MockSettings? = null, apply: T.() -> Unit = {}): T = Mockito.mock(T::class.java, settings ?: withSettings()).apply(apply) /** * Helper function for stubbing methods without the need to use backticks. * - * @see Mockito.when + * Avoid. It is preferable to provide stubbing at creation time using the [mock] lambda argument. + * + * @see org.mockito.kotlin.whenever */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "whenever(methodCall)", imports = ["org.mockito.kotlin.whenever"]), + level = WARNING +) fun <T> whenever(methodCall: T): OngoingStubbing<T> = `when`(methodCall) +/** + * __Deprecation note__ + * + * Replace with `KStubbing<T>.on` within [org.mockito.kotlin.mock] { stubbing } + * + * @see org.mockito.kotlin.mock + * @see org.mockito.kotlin.KStubbing.on + */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "whenever(mock)", imports = ["org.mockito.kotlin.whenever"]), + level = WARNING +) fun <T> Stubber.whenever(mock: T): T = `when`(mock) /** @@ -101,6 +182,7 @@ fun <T> Stubber.whenever(mock: T): T = `when`(mock) * * java.lang.NullPointerException: capture() must not be null */ +@Deprecated("Replace with mockito-kotlin. See http://go/mockito-kotlin", level = WARNING) class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) { private val wrapped: ArgumentCaptor<T> = ArgumentCaptor.forClass(clazz) fun capture(): T = wrapped.capture() @@ -114,7 +196,14 @@ class KotlinArgumentCaptor<T> constructor(clazz: Class<T>) { * Helper function for creating an argumentCaptor in kotlin. * * Generic T is nullable because implicitly bounded by Any?. + * + * @see org.mockito.kotlin.argumentCaptor */ +@Deprecated( + "Replace with mockito-kotlin. See http://go/mockito-kotlin", + ReplaceWith(expression = "argumentCaptor()", imports = ["org.mockito.kotlin.argumentCaptor"]), + level = WARNING +) inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> = KotlinArgumentCaptor(T::class.java) @@ -130,6 +219,7 @@ inline fun <reified T : Any> kotlinArgumentCaptor(): KotlinArgumentCaptor<T> = * * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException. */ +@Deprecated("Replace with mockito-kotlin", level = WARNING) inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T = kotlinArgumentCaptor<T>().apply { block() }.value @@ -142,6 +232,10 @@ inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> * becomes: * * val capturedList = captureMany<Foo> { verify(...).someMethod(capture()) } + * + * @see org.mockito.kotlin.verify */ +@Suppress("DeprecatedCallableAddReplaceWith") +@Deprecated("Replace with mockito-kotlin. See http://go/mockito-kotlin", level = WARNING) inline fun <reified T : Any> captureMany(block: KotlinArgumentCaptor<T>.() -> Unit): List<T> = kotlinArgumentCaptor<T>().apply { block() }.allValues diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java index d798b3b13341..0364e0595999 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeBatteryController.java @@ -16,8 +16,8 @@ package com.android.systemui.utils.leaks; import android.os.Bundle; import android.testing.LeakCheck; -import android.view.View; +import com.android.systemui.animation.Expandable; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; @@ -61,7 +61,7 @@ public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCal * Note: this method ignores the View argument */ @Override - public void setPowerSaveMode(boolean powerSave, View view) { + public void setPowerSaveMode(boolean powerSave, Expandable expandable) { setPowerSaveMode(powerSave); } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaControllerKosmos.kt index 5db17243c4e3..546a797482a5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaControllerKosmos.kt @@ -19,8 +19,10 @@ package com.android.systemui.volume import android.content.packageManager import android.content.pm.ApplicationInfo import android.media.AudioAttributes +import android.media.VolumeProvider import android.media.session.MediaController import android.media.session.MediaSession +import android.media.session.PlaybackState import com.android.systemui.kosmos.Kosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -28,6 +30,18 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever private const val LOCAL_PACKAGE = "local.test.pkg" +var Kosmos.localPlaybackInfo by + Kosmos.Fixture { + MediaController.PlaybackInfo( + MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, + VolumeProvider.VOLUME_CONTROL_ABSOLUTE, + 10, + 3, + AudioAttributes.Builder().build(), + "", + ) + } +var Kosmos.localPlaybackStateBuilder by Kosmos.Fixture { PlaybackState.Builder() } var Kosmos.localMediaController: MediaController by Kosmos.Fixture { val appInfo: ApplicationInfo = mock { @@ -39,22 +53,25 @@ var Kosmos.localMediaController: MediaController by val localSessionToken: MediaSession.Token = MediaSession.Token(0, mock {}) mock { whenever(packageName).thenReturn(LOCAL_PACKAGE) - whenever(playbackInfo) - .thenReturn( - MediaController.PlaybackInfo( - MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, - 0, - 0, - 0, - AudioAttributes.Builder().build(), - "", - ) - ) + whenever(playbackInfo).thenReturn(localPlaybackInfo) + whenever(playbackState).thenReturn(localPlaybackStateBuilder.build()) whenever(sessionToken).thenReturn(localSessionToken) } } private const val REMOTE_PACKAGE = "remote.test.pkg" +var Kosmos.remotePlaybackInfo by + Kosmos.Fixture { + MediaController.PlaybackInfo( + MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, + VolumeProvider.VOLUME_CONTROL_ABSOLUTE, + 10, + 7, + AudioAttributes.Builder().build(), + "", + ) + } +var Kosmos.remotePlaybackStateBuilder by Kosmos.Fixture { PlaybackState.Builder() } var Kosmos.remoteMediaController: MediaController by Kosmos.Fixture { val appInfo: ApplicationInfo = mock { @@ -66,17 +83,8 @@ var Kosmos.remoteMediaController: MediaController by val remoteSessionToken: MediaSession.Token = MediaSession.Token(0, mock {}) mock { whenever(packageName).thenReturn(REMOTE_PACKAGE) - whenever(playbackInfo) - .thenReturn( - MediaController.PlaybackInfo( - MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, - 0, - 0, - 0, - AudioAttributes.Builder().build(), - "", - ) - ) + whenever(playbackInfo).thenReturn(remotePlaybackInfo) + whenever(playbackState).thenReturn(remotePlaybackStateBuilder.build()) whenever(sessionToken).thenReturn(remoteSessionToken) } } 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 fa3a19bae655..1b58582a806f 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 @@ -18,10 +18,7 @@ package com.android.systemui.volume import android.content.packageManager import android.content.pm.ApplicationInfo -import android.os.Handler -import android.testing.TestableLooper 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 @@ -30,13 +27,13 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.volume.data.repository.FakeLocalMediaRepository import com.android.systemui.volume.data.repository.FakeMediaControllerRepository import com.android.systemui.volume.panel.component.mediaoutput.data.repository.FakeLocalMediaRepositoryFactory -import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor +import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaControllerInteractor val Kosmos.localMediaRepository by Kosmos.Fixture { FakeLocalMediaRepository() } -val Kosmos.localMediaRepositoryFactory: LocalMediaRepositoryFactory by +val Kosmos.localMediaRepositoryFactory by Kosmos.Fixture { FakeLocalMediaRepositoryFactory { localMediaRepository } } val Kosmos.mediaOutputActionsInteractor by @@ -55,6 +52,7 @@ val Kosmos.mediaOutputInteractor by testScope.backgroundScope, testScope.testScheduler, mediaControllerRepository, + mediaControllerInteractor, ) } @@ -62,7 +60,7 @@ val Kosmos.mediaDeviceSessionInteractor by Kosmos.Fixture { MediaDeviceSessionInteractor( testScope.testScheduler, - Handler(TestableLooper.get(testCase).looper), + mediaControllerInteractor, mediaControllerRepository, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt index 617fc5258ec7..6b27079cb648 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt @@ -17,6 +17,7 @@ package com.android.systemui.volume.data.repository import android.media.AudioDeviceInfo +import android.media.AudioManager import com.android.settingslib.volume.data.repository.AudioRepository import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel @@ -29,10 +30,10 @@ import kotlinx.coroutines.flow.update class FakeAudioRepository : AudioRepository { - private val mutableMode = MutableStateFlow(0) + private val mutableMode = MutableStateFlow(AudioManager.MODE_NORMAL) override val mode: StateFlow<Int> = mutableMode.asStateFlow() - private val mutableRingerMode = MutableStateFlow(RingerMode(0)) + private val mutableRingerMode = MutableStateFlow(RingerMode(AudioManager.RINGER_MODE_NORMAL)) override val ringerMode: StateFlow<RingerMode> = mutableRingerMode.asStateFlow() private val mutableCommunicationDevice = MutableStateFlow<AudioDeviceInfo?>(null) @@ -53,7 +54,7 @@ class FakeAudioRepository : AudioRepository { audioStream = audioStream, volume = 0, minVolume = 0, - maxVolume = 0, + maxVolume = 10, isAffectedByRingerMode = false, isMuted = false, ) @@ -67,8 +68,14 @@ class FakeAudioRepository : AudioRepository { getAudioStreamModelState(audioStream).update { it.copy(volume = volume) } } - override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) { - getAudioStreamModelState(audioStream).update { it.copy(isMuted = isMuted) } + override suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean { + val modelState = getAudioStreamModelState(audioStream) + return if (modelState.value.isMuted == isMuted) { + false + } else { + modelState.update { it.copy(isMuted = isMuted) } + true + } } override suspend fun getLastAudibleVolume(audioStream: AudioStream): Int = 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/anc/VolumePanelAncKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt index f9b7e69eea7d..2b5d1b9757bc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/VolumePanelAncKosmos.kt @@ -20,10 +20,13 @@ import androidx.slice.SliceViewManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.util.mockito.mock +import com.android.systemui.volume.domain.interactor.audioOutputInteractor import com.android.systemui.volume.panel.component.anc.data.repository.FakeAncSliceRepository import com.android.systemui.volume.panel.component.anc.domain.interactor.AncSliceInteractor var Kosmos.sliceViewManager: SliceViewManager by Kosmos.Fixture { mock {} } val Kosmos.ancSliceRepository by Kosmos.Fixture { FakeAncSliceRepository() } val Kosmos.ancSliceInteractor by - Kosmos.Fixture { AncSliceInteractor(ancSliceRepository, testScope.backgroundScope) } + Kosmos.Fixture { + AncSliceInteractor(audioOutputInteractor, ancSliceRepository, testScope.backgroundScope) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt index d4a72b437fd8..ebe68505e343 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/anc/data/repository/FakeAncSliceRepository.kt @@ -16,6 +16,7 @@ package com.android.systemui.volume.panel.component.anc.data.repository +import android.bluetooth.BluetoothDevice import androidx.slice.Slice import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -24,7 +25,12 @@ class FakeAncSliceRepository : AncSliceRepository { private val sliceByWidth = mutableMapOf<Int, MutableStateFlow<Slice?>>() - override fun ancSlice(width: Int, isCollapsed: Boolean, hideLabel: Boolean): Flow<Slice?> { + override fun ancSlice( + device: BluetoothDevice, + width: Int, + isCollapsed: Boolean, + hideLabel: Boolean + ): Flow<Slice?> { return sliceByWidth.getOrPut(width) { MutableStateFlow(null) } } 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/data/repository/FakeLocalMediaRepositoryFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt index 1b3480c423e4..9c902cf57fde 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt @@ -18,9 +18,15 @@ package com.android.systemui.volume.panel.component.mediaoutput.data.repository import com.android.settingslib.volume.data.repository.LocalMediaRepository -class FakeLocalMediaRepositoryFactory( - val provider: (packageName: String?) -> LocalMediaRepository -) : LocalMediaRepositoryFactory { +class FakeLocalMediaRepositoryFactory(private val defaultProvider: () -> LocalMediaRepository) : + LocalMediaRepositoryFactory { - override fun create(packageName: String?): LocalMediaRepository = provider(packageName) + private val repositories = mutableMapOf<String, LocalMediaRepository>() + + fun setLocalMediaRepository(packageName: String, localMediaRepository: LocalMediaRepository) { + repositories[packageName] = localMediaRepository + } + + override fun create(packageName: String?): LocalMediaRepository = + repositories[packageName] ?: defaultProvider() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.kt new file mode 100644 index 000000000000..f03ec01bd36f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/FakeMediaControllerInteractor.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.volume.panel.component.mediaoutput.domain.interactor + +import android.media.session.MediaController +import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow + +class FakeMediaControllerInteractor : MediaControllerInteractor { + + private val stateChanges = MutableSharedFlow<MediaControllerChangeModel>(replay = 1) + + override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> = + stateChanges + + fun updateState(change: MediaControllerChangeModel) { + stateChanges.tryEmit(change) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt new file mode 100644 index 000000000000..652b3ea984e7 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.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.volume.panel.component.mediaoutput.domain.interactor + +import android.os.Handler +import android.os.looper +import com.android.systemui.kosmos.Kosmos + +var Kosmos.mediaControllerInteractor: MediaControllerInteractor by + Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt new file mode 100644 index 000000000000..40296099bfe0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.volume.panel.component.mediaoutput.domain.interactor + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothDevice +import android.graphics.drawable.TestStubDrawable +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.media.BluetoothMediaDevice +import com.android.settingslib.media.MediaDevice +import com.android.settingslib.media.PhoneMediaDevice +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever + +@SuppressLint("StaticFieldLeak") // These are mocks +object TestMediaDevicesFactory { + + fun builtInMediaDevice(): MediaDevice = mock { + whenever(name).thenReturn("built_in_media") + whenever(icon).thenReturn(TestStubDrawable()) + } + + fun wiredMediaDevice(): MediaDevice = + mock<PhoneMediaDevice> { + whenever(deviceType) + .thenReturn(MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE) + whenever(name).thenReturn("wired_media") + whenever(icon).thenReturn(TestStubDrawable()) + } + + fun bluetoothMediaDevice(): MediaDevice { + val bluetoothDevice = mock<BluetoothDevice>() + val cachedBluetoothDevice: CachedBluetoothDevice = mock { + whenever(isHearingAidDevice).thenReturn(true) + whenever(address).thenReturn("bt_media_device") + whenever(device).thenReturn(bluetoothDevice) + } + return mock<BluetoothMediaDevice> { + whenever(name).thenReturn("bt_media") + whenever(icon).thenReturn(TestStubDrawable()) + whenever(cachedDevice).thenReturn(cachedBluetoothDevice) + } + } +} 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/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt index 2bc2db3ba629..fe102448a6cb 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldRemoteModule.kt @@ -53,8 +53,8 @@ class UnfoldRemoteModule { @UnfoldMain fun provideMainRotationChangeProvider( rotationChangeProviderFactory: RotationChangeProvider.Factory, - @UnfoldMain mainHandler: Handler, + @UnfoldMain callbackHandler: Handler, ): RotationChangeProvider { - return rotationChangeProviderFactory.create(mainHandler) + return rotationChangeProviderFactory.create(callbackHandler) } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt index 31b7ccca49ac..f382070f8fa7 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt @@ -87,6 +87,7 @@ interface RemoteUnfoldSharedComponent { @BindsInstance @UnfoldMain executor: Executor, @BindsInstance @UnfoldMain handler: Handler, @BindsInstance @UnfoldSingleThreadBg singleThreadBgExecutor: Executor, + @BindsInstance @UnfoldBg bgHandler: Handler, @BindsInstance displayManager: DisplayManager, @BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String, ): RemoteUnfoldSharedComponent diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt index 1b7e71a42c22..f83ea845809c 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt @@ -270,9 +270,9 @@ internal class UnfoldRotationProviderInternalModule { @UnfoldMain fun provideRotationChangeProvider( rotationChangeProviderFactory: RotationChangeProvider.Factory, - @UnfoldMain mainHandler: Handler, + @UnfoldMain callbackHandler: Handler, ): RotationChangeProvider { - return rotationChangeProviderFactory.create(mainHandler) + return rotationChangeProviderFactory.create(callbackHandler) } @Provides @@ -280,8 +280,9 @@ internal class UnfoldRotationProviderInternalModule { @UnfoldBg fun provideBgRotationChangeProvider( rotationChangeProviderFactory: RotationChangeProvider.Factory, - @UnfoldBg bgHandler: Handler, + @UnfoldBg callbackHandler: Handler, ): RotationChangeProvider { - return rotationChangeProviderFactory.create(bgHandler) + // For UnfoldBg RotationChangeProvider we use bgHandler as callbackHandler + return rotationChangeProviderFactory.create(callbackHandler) } } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt index 1cbaf3135c4d..8a4f9857603a 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt @@ -77,6 +77,7 @@ fun createRemoteUnfoldSharedComponent( mainExecutor: Executor, mainHandler: Handler, singleThreadBgExecutor: Executor, + bgHandler: Handler, tracingTagPrefix: String, displayManager: DisplayManager, ): RemoteUnfoldSharedComponent = @@ -87,6 +88,7 @@ fun createRemoteUnfoldSharedComponent( mainExecutor, mainHandler, singleThreadBgExecutor, + bgHandler, displayManager, tracingTagPrefix, ) diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt index 77f637bb8ba1..a10097427ae5 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt @@ -20,6 +20,7 @@ import android.os.Handler import android.util.Log import androidx.annotation.FloatRange import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread import androidx.core.util.Consumer import com.android.systemui.unfold.compat.INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP import com.android.systemui.unfold.config.UnfoldTransitionConfig @@ -215,6 +216,7 @@ constructor( } private inner class FoldRotationListener : RotationChangeProvider.RotationListener { + @WorkerThread override fun onRotationChanged(newRotation: Int) { assertInProgressThread() if (isTransitionInProgress) cancelAnimation() diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt index bb91f9b8cf0b..4f3aee99c206 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt @@ -21,6 +21,8 @@ import android.hardware.display.DisplayManager import android.os.Handler import android.os.RemoteException import android.os.Trace +import androidx.annotation.AnyThread +import com.android.systemui.unfold.dagger.UnfoldBg import com.android.systemui.unfold.util.CallbackController import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -35,7 +37,8 @@ class RotationChangeProvider constructor( private val displayManager: DisplayManager, private val context: Context, - @Assisted private val handler: Handler, + @UnfoldBg private val bgHandler: Handler, + @Assisted private val callbackHandler: Handler, ) : CallbackController<RotationChangeProvider.RotationListener> { private val listeners = mutableListOf<RotationListener>() @@ -44,7 +47,7 @@ constructor( private var lastRotation: Int? = null override fun addCallback(listener: RotationListener) { - handler.post { + bgHandler.post { if (listeners.isEmpty()) { subscribeToRotation() } @@ -53,7 +56,7 @@ constructor( } override fun removeCallback(listener: RotationListener) { - handler.post { + bgHandler.post { listeners -= listener if (listeners.isEmpty()) { unsubscribeToRotation() @@ -64,7 +67,7 @@ constructor( private fun subscribeToRotation() { try { - displayManager.registerDisplayListener(displayListener, handler) + displayManager.registerDisplayListener(displayListener, callbackHandler) } catch (e: RemoteException) { throw e.rethrowFromSystemServer() } @@ -80,8 +83,11 @@ constructor( /** Gets notified of rotation changes. */ fun interface RotationListener { - /** Called once rotation changes. */ - fun onRotationChanged(newRotation: Int) + /** + * Called once rotation changes. This callback is called on the handler provided to + * [RotationChangeProvider.Factory.create]. + */ + @AnyThread fun onRotationChanged(newRotation: Int) } private inner class RotationDisplayListener : DisplayManager.DisplayListener { @@ -110,7 +116,7 @@ constructor( @AssistedFactory interface Factory { - /** Creates a new [RotationChangeProvider] that provides updated using [handler]. */ - fun create(handler: Handler): RotationChangeProvider + /** Creates a new [RotationChangeProvider] that provides updated using [callbackHandler]. */ + fun create(callbackHandler: Handler): RotationChangeProvider } } diff --git a/proto/src/am_capabilities.proto b/proto/src/am_capabilities.proto index fc9f7a4590bd..c2b3ac2aaa78 100644 --- a/proto/src/am_capabilities.proto +++ b/proto/src/am_capabilities.proto @@ -15,8 +15,16 @@ message FrameworkCapability { string name = 1; } +message VMInfo { + // The value of the "java.vm.name" system property + string name = 1; + // The value of the "java.vm.version" system property + string version = 2; +} + message Capabilities { repeated Capability values = 1; repeated VMCapability vm_capabilities = 2; repeated FrameworkCapability framework_capabilities = 3; + VMInfo vm_info = 4; } diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index e06f40070a0a..95cbb6b2130a 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -16,6 +16,38 @@ filegroup { visibility: ["//visibility:public"], } +filegroup { + name: "ravenwood-services-policies", + srcs: [ + "texts/ravenwood-services-policies.txt", + ], + visibility: ["//visibility:public"], +} + +filegroup { + name: "ravenwood-framework-policies", + srcs: [ + "texts/ravenwood-framework-policies.txt", + ], + visibility: ["//visibility:public"], +} + +filegroup { + name: "ravenwood-standard-options", + srcs: [ + "texts/ravenwood-standard-options.txt", + ], + visibility: ["//visibility:public"], +} + +filegroup { + name: "ravenwood-annotation-allowed-classes", + srcs: [ + "texts/ravenwood-annotation-allowed-classes.txt", + ], + visibility: ["//visibility:public"], +} + java_library { name: "ravenwood-annotations-lib", srcs: [":ravenwood-annotations"], @@ -189,7 +221,9 @@ sh_test_host { data: [ ":framework-minus-apex.ravenwood.stats", ":framework-minus-apex.ravenwood.apis", + ":framework-minus-apex.ravenwood.keep_all", ":services.core.ravenwood.stats", ":services.core.ravenwood.apis", + ":services.core.ravenwood.keep_all", ], } diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java new file mode 100644 index 000000000000..4992c4bcc77a --- /dev/null +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodBaseContext.java @@ -0,0 +1,753 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package android.platform.test.ravenwood; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.IntentSender.SendIntentException; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.UserHandle; +import android.view.Display; +import android.view.DisplayAdjustments; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A subclass of Context with all the abstract methods replaced with concrete methods. + * + * <p>In order to make sure it implements all the abstract methods, we intentionally keep it + * non-abstract. + */ +public class RavenwoodBaseContext extends Context { + RavenwoodBaseContext() { + // Only usable by ravenwood. + } + + private static RuntimeException notSupported() { + return new RuntimeException("This Context API is not yet supported under" + + " the Ravenwood deviceless testing environment. Contact g/ravenwood"); + } + + @Override + public AssetManager getAssets() { + throw notSupported(); + } + + @Override + public Resources getResources() { + throw notSupported(); + } + + @Override + public PackageManager getPackageManager() { + throw notSupported(); + } + + @Override + public ContentResolver getContentResolver() { + throw notSupported(); + } + + @Override + public Looper getMainLooper() { + throw notSupported(); + } + + @Override + public Context getApplicationContext() { + throw notSupported(); + } + + @Override + public void setTheme(int resid) { + throw notSupported(); + } + + @Override + public Theme getTheme() { + throw notSupported(); + } + + @Override + public ClassLoader getClassLoader() { + throw notSupported(); + } + + @Override + public String getPackageName() { + throw notSupported(); + } + + @Override + public String getBasePackageName() { + throw notSupported(); + } + + @Override + public ApplicationInfo getApplicationInfo() { + throw notSupported(); + } + + @Override + public String getPackageResourcePath() { + throw notSupported(); + } + + @Override + public String getPackageCodePath() { + throw notSupported(); + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + throw notSupported(); + } + + @Override + public SharedPreferences getSharedPreferences(File file, int mode) { + throw notSupported(); + } + + @Override + public boolean moveSharedPreferencesFrom(Context sourceContext, String name) { + throw notSupported(); + } + + @Override + public boolean deleteSharedPreferences(String name) { + throw notSupported(); + } + + @Override + public void reloadSharedPreferences() { + throw notSupported(); + } + + @Override + public FileInputStream openFileInput(String name) throws FileNotFoundException { + throw notSupported(); + } + + @Override + public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { + throw notSupported(); + } + + @Override + public boolean deleteFile(String name) { + throw notSupported(); + } + + @Override + public File getFileStreamPath(String name) { + throw notSupported(); + } + + @Override + public File getSharedPreferencesPath(String name) { + throw notSupported(); + } + + @Override + public File getDataDir() { + throw notSupported(); + } + + @Override + public File getFilesDir() { + throw notSupported(); + } + + @Override + public File getNoBackupFilesDir() { + throw notSupported(); + } + + @Override + public File getExternalFilesDir(String type) { + throw notSupported(); + } + + @Override + public File[] getExternalFilesDirs(String type) { + throw notSupported(); + } + + @Override + public File getObbDir() { + throw notSupported(); + } + + @Override + public File[] getObbDirs() { + throw notSupported(); + } + + @Override + public File getCacheDir() { + throw notSupported(); + } + + @Override + public File getCodeCacheDir() { + throw notSupported(); + } + + @Override + public File getExternalCacheDir() { + throw notSupported(); + } + + @Override + public File getPreloadsFileCache() { + throw notSupported(); + } + + @Override + public File[] getExternalCacheDirs() { + throw notSupported(); + } + + @Override + public File[] getExternalMediaDirs() { + throw notSupported(); + } + + @Override + public String[] fileList() { + throw notSupported(); + } + + @Override + public File getDir(String name, int mode) { + throw notSupported(); + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) { + throw notSupported(); + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, + DatabaseErrorHandler errorHandler) { + throw notSupported(); + } + + @Override + public boolean moveDatabaseFrom(Context sourceContext, String name) { + throw notSupported(); + } + + @Override + public boolean deleteDatabase(String name) { + throw notSupported(); + } + + @Override + public File getDatabasePath(String name) { + throw notSupported(); + } + + @Override + public String[] databaseList() { + throw notSupported(); + } + + @Override + public Drawable getWallpaper() { + throw notSupported(); + } + + @Override + public Drawable peekWallpaper() { + throw notSupported(); + } + + @Override + public int getWallpaperDesiredMinimumWidth() { + throw notSupported(); + } + + @Override + public int getWallpaperDesiredMinimumHeight() { + throw notSupported(); + } + + @Override + public void setWallpaper(Bitmap bitmap) throws IOException { + throw notSupported(); + } + + @Override + public void setWallpaper(InputStream data) throws IOException { + throw notSupported(); + } + + @Override + public void clearWallpaper() throws IOException { + throw notSupported(); + } + + @Override + public void startActivity(Intent intent) { + throw notSupported(); + } + + @Override + public void startActivity(Intent intent, Bundle options) { + throw notSupported(); + } + + @Override + public void startActivities(Intent[] intents) { + throw notSupported(); + } + + @Override + public void startActivities(Intent[] intents, Bundle options) { + throw notSupported(); + } + + @Override + public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, + int flagsValues, int extraFlags) throws SendIntentException { + throw notSupported(); + } + + @Override + public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, + int flagsValues, int extraFlags, Bundle options) throws SendIntentException { + throw notSupported(); + } + + @Override + public void sendBroadcast(Intent intent) { + throw notSupported(); + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user, + String[] receiverPermissions) { + throw notSupported(); + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission, int appOp) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, + String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcast(Intent intent, String receiverPermission, + int appOp, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, + String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, + Bundle options) { + throw notSupported(); + } + + @Override + public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, + int appOp) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp, Bundle options, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, + String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void sendStickyBroadcast(Intent intent) { + throw notSupported(); + } + + @Override + public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + throw notSupported(); + + } + + @Override + public void removeStickyBroadcast(Intent intent) { + throw notSupported(); + + } + + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { + throw notSupported(); + } + + @Override + public void sendStickyBroadcastAsUser(Intent intent, UserHandle user, Bundle options) { + throw notSupported(); + + } + + @Override + public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, + BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, + String initialData, Bundle initialExtras) { + throw notSupported(); + } + + @Override + public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { + throw notSupported(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + throw notSupported(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) { + throw notSupported(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + throw notSupported(); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler, int flags) { + throw notSupported(); + } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler) { + throw notSupported(); + } + + @Override + public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user, + IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) { + throw notSupported(); + } + + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + throw notSupported(); + } + + @Override + public ComponentName startService(Intent service) { + throw notSupported(); + } + + @Override + public ComponentName startForegroundService(Intent service) { + throw notSupported(); + } + + @Override + public ComponentName startForegroundServiceAsUser(Intent service, UserHandle user) { + throw notSupported(); + } + + @Override + public boolean stopService(Intent service) { + throw notSupported(); + } + + @Override + public ComponentName startServiceAsUser(Intent service, UserHandle user) { + throw notSupported(); + } + + @Override + public boolean stopServiceAsUser(Intent service, UserHandle user) { + throw notSupported(); + } + + @Override + public boolean bindService(Intent service, ServiceConnection conn, int flags) { + throw notSupported(); + } + + @Override + public void unbindService(ServiceConnection conn) { + throw notSupported(); + } + + @Override + public boolean startInstrumentation(ComponentName className, String profileFile, + Bundle arguments) { + throw notSupported(); + } + + @Override + public Object getSystemService(String name) { + throw notSupported(); + } + + @Override + public String getSystemServiceName(Class<?> serviceClass) { + throw notSupported(); + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + throw notSupported(); + } + + @Override + public int checkPermission(String permission, int pid, int uid, IBinder callerToken) { + throw notSupported(); + } + + @Override + public int checkCallingPermission(String permission) { + throw notSupported(); + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + throw notSupported(); + } + + @Override + public int checkSelfPermission(String permission) { + throw notSupported(); + } + + @Override + public void enforcePermission(String permission, int pid, int uid, String message) { + throw notSupported(); + } + + @Override + public void enforceCallingPermission(String permission, String message) { + throw notSupported(); + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + throw notSupported(); + } + + @Override + public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public void revokeUriPermission(Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public void revokeUriPermission(String toPackage, Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + throw notSupported(); + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags, IBinder callerToken) { + throw notSupported(); + } + + @Override + public int checkCallingUriPermission(Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { + throw notSupported(); + } + + @Override + public int checkUriPermission(Uri uri, String readPermission, String writePermission, + int pid, int uid, int modeFlags) { + throw notSupported(); + } + + @Override + public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) { + throw notSupported(); + } + + @Override + public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) { + throw notSupported(); + } + + @Override + public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) { + throw notSupported(); + } + + @Override + public void enforceUriPermission(Uri uri, String readPermission, String writePermission, + int pid, int uid, int modeFlags, String message) { + throw notSupported(); + } + + @Override + public Context createPackageContext(String packageName, int flags) + throws NameNotFoundException { + throw notSupported(); + } + + @Override + public Context createApplicationContext(ApplicationInfo application, int flags) + throws NameNotFoundException { + throw notSupported(); + } + + @Override + public Context createContextForSplit(String splitName) throws NameNotFoundException { + throw notSupported(); + } + + @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + throw notSupported(); + } + + @Override + public Context createDisplayContext(Display display) { + throw notSupported(); + } + + @Override + public Context createDeviceProtectedStorageContext() { + throw notSupported(); + } + + @Override + public Context createCredentialProtectedStorageContext() { + throw notSupported(); + } + + @Override + public DisplayAdjustments getDisplayAdjustments(int displayId) { + throw notSupported(); + } + + @Override + public int getDisplayId() { + throw notSupported(); + } + + @Override + public void updateDisplay(int displayId) { + throw notSupported(); + } + + @Override + public boolean isDeviceProtectedStorage() { + throw notSupported(); + } + + @Override + public boolean isCredentialProtectedStorage() { + throw notSupported(); + } + + @Override + public boolean canLoadUnsafeResources() { + throw notSupported(); + } +} diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java index 109ef76b535f..1dd5e1ddd630 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodContext.java @@ -28,7 +28,6 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.ravenwood.example.BlueManager; import android.ravenwood.example.RedManager; -import android.test.mock.MockContext; import android.util.ArrayMap; import android.util.Singleton; @@ -36,7 +35,7 @@ import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Supplier; -public class RavenwoodContext extends MockContext { +public class RavenwoodContext extends RavenwoodBaseContext { private final String mPackageName; private final HandlerThread mMainThread; diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java index 96b7057d25ec..69ff262fe915 100644 --- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java +++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java @@ -162,20 +162,23 @@ public class ClassLoadHook { android.graphics.Interpolator.class, android.graphics.Matrix.class, android.graphics.Path.class, + android.graphics.Color.class, + android.graphics.ColorSpace.class, }; /** - * @return if a given class has any native method or not. + * @return if a given class and its nested classes, if any, have any native method or not. */ private static boolean hasNativeMethod(Class<?> clazz) { - for (var method : clazz.getDeclaredMethods()) { - if (Modifier.isNative(method.getModifiers())) { - return true; + for (var nestedClass : clazz.getNestMembers()) { + for (var method : nestedClass.getDeclaredMethods()) { + if (Modifier.isNative(method.getModifiers())) { + return true; + } } } return false; } - /** * Create a list of classes as comma-separated that require JNI methods to be set up from * a given class list, ignoring classes with no native methods. 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/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh index beacde282d49..43b61a46b747 100755 --- a/ravenwood/scripts/ravenwood-stats-collector.sh +++ b/ravenwood/scripts/ravenwood-stats-collector.sh @@ -18,12 +18,20 @@ set -e # Output files -stats=/tmp/ravenwood-stats-all.csv -apis=/tmp/ravenwood-apis-all.csv +out_dir=/tmp/ravenwood +stats=$out_dir/ravenwood-stats-all.csv +apis=$out_dir/ravenwood-apis-all.csv +keep_all_dir=$out_dir/ravenwood-keep-all/ + +rm -fr $out_dir +mkdir -p $out_dir +mkdir -p $keep_all_dir # Where the input files are. path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/ +timestamp="$(date --iso-8601=seconds)" + m() { ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@" } @@ -39,15 +47,15 @@ dump() { local jar=$1 local file=$2 - # Use sed to remove the header + prepend the jar filename. - sed -e '1d' -e "s/^/$jar,/" $file + # Remove the header row, and prepend the columns. + sed -e '1d' -e "s/^/$jar,$timestamp,/" $file } collect_stats() { local out="$1" { # Copy the header, with the first column appended. - echo -n "Jar," + echo -n "Jar,Generated Date," head -n 1 hoststubgen_framework-minus-apex_stats.csv dump "framework-minus-apex" hoststubgen_framework-minus-apex_stats.csv @@ -61,7 +69,7 @@ collect_apis() { local out="$1" { # Copy the header, with the first column appended. - echo -n "Jar," + echo -n "Jar,Generated Date," head -n 1 hoststubgen_framework-minus-apex_apis.csv dump "framework-minus-apex" hoststubgen_framework-minus-apex_apis.csv @@ -74,3 +82,7 @@ collect_apis() { collect_stats $stats collect_apis $apis + +cp *keep_all.txt $keep_all_dir +echo "Keep all files created at:" +find $keep_all_dir -type f
\ No newline at end of file diff --git a/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt b/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt deleted file mode 100644 index 371c3acab144..000000000000 --- a/ravenwood/texts/framework-minus-apex-ravenwood-policies.txt +++ /dev/null @@ -1,59 +0,0 @@ -# Ravenwood "policy" file for framework-minus-apex. - -# Keep all AIDL interfaces -class :aidl stubclass - -# Keep all feature flag implementations -class :feature_flags stubclass - -# Keep all sysprops generated code implementations -class :sysprops stubclass - -# Exported to Mainline modules; cannot use annotations -class com.android.internal.util.FastXmlSerializer stubclass -class com.android.internal.util.FileRotator stubclass -class com.android.internal.util.HexDump stubclass -class com.android.internal.util.IndentingPrintWriter stubclass -class com.android.internal.util.LocalLog stubclass -class com.android.internal.util.MessageUtils stubclass -class com.android.internal.util.TokenBucket stubclass -class android.os.HandlerExecutor stubclass -class android.util.BackupUtils stubclass -class android.util.IndentingPrintWriter stubclass -class android.util.LocalLog stubclass -class android.util.Pair stubclass -class android.util.Rational stubclass - -# From modules-utils; cannot use annotations -class com.android.internal.util.Preconditions stubclass -class com.android.internal.logging.InstanceId stubclass -class com.android.internal.logging.InstanceIdSequence stubclass -class com.android.internal.logging.UiEvent stubclass -class com.android.internal.logging.UiEventLogger stubclass - -# From modules-utils; cannot use annotations -class com.android.modules.utils.BinaryXmlPullParser stubclass -class com.android.modules.utils.BinaryXmlSerializer stubclass -class com.android.modules.utils.FastDataInput stubclass -class com.android.modules.utils.FastDataOutput stubclass -class com.android.modules.utils.ModifiedUtf8 stubclass -class com.android.modules.utils.TypedXmlPullParser stubclass -class com.android.modules.utils.TypedXmlSerializer stubclass - -# Uri -class android.net.Uri stubclass -class android.net.UriCodec stubclass - -# Telephony -class android.telephony.PinResult stubclass - -# Just enough to support mocking, no further functionality -class android.content.BroadcastReceiver stub - method <init> ()V stub -class android.content.Context stub - method <init> ()V stub - method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; stub -class android.content.pm.PackageManager stub - method <init> ()V stub -class android.text.ClipboardManager stub - method <init> ()V stub diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt index e452299373f8..f3172ae3090d 100644 --- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt @@ -1,5 +1,7 @@ # Only classes listed here can use the Ravenwood annotations. +com.android.internal.ravenwood.* + com.android.internal.display.BrightnessSynchronizer com.android.internal.util.ArrayUtils com.android.internal.logging.MetricsLogger @@ -237,6 +239,8 @@ android.text.TextUtils$SimpleStringSplitter android.accounts.Account android.graphics.Bitmap$Config +android.graphics.Color +android.graphics.ColorSpace android.graphics.Insets android.graphics.Interpolator android.graphics.Matrix diff --git a/ravenwood/texts/ravenwood-framework-policies.txt b/ravenwood/texts/ravenwood-framework-policies.txt new file mode 100644 index 000000000000..9d29a051d092 --- /dev/null +++ b/ravenwood/texts/ravenwood-framework-policies.txt @@ -0,0 +1,59 @@ +# Ravenwood "policy" file for framework-minus-apex. + +# Keep all AIDL interfaces +class :aidl keepclass + +# Keep all feature flag implementations +class :feature_flags keepclass + +# Keep all sysprops generated code implementations +class :sysprops keepclass + +# Exported to Mainline modules; cannot use annotations +class com.android.internal.util.FastXmlSerializer keepclass +class com.android.internal.util.FileRotator keepclass +class com.android.internal.util.HexDump keepclass +class com.android.internal.util.IndentingPrintWriter keepclass +class com.android.internal.util.LocalLog keepclass +class com.android.internal.util.MessageUtils keepclass +class com.android.internal.util.TokenBucket keepclass +class android.os.HandlerExecutor keepclass +class android.util.BackupUtils keepclass +class android.util.IndentingPrintWriter keepclass +class android.util.LocalLog keepclass +class android.util.Pair keepclass +class android.util.Rational keepclass + +# From modules-utils; cannot use annotations +class com.android.internal.util.Preconditions keepclass +class com.android.internal.logging.InstanceId keepclass +class com.android.internal.logging.InstanceIdSequence keepclass +class com.android.internal.logging.UiEvent keepclass +class com.android.internal.logging.UiEventLogger keepclass + +# From modules-utils; cannot use annotations +class com.android.modules.utils.BinaryXmlPullParser keepclass +class com.android.modules.utils.BinaryXmlSerializer keepclass +class com.android.modules.utils.FastDataInput keepclass +class com.android.modules.utils.FastDataOutput keepclass +class com.android.modules.utils.ModifiedUtf8 keepclass +class com.android.modules.utils.TypedXmlPullParser keepclass +class com.android.modules.utils.TypedXmlSerializer keepclass + +# Uri +class android.net.Uri keepclass +class android.net.UriCodec keepclass + +# Telephony +class android.telephony.PinResult keepclass + +# Just enough to support mocking, no further functionality +class android.content.BroadcastReceiver keep + method <init> ()V keep +class android.content.Context keep + method <init> ()V keep + method getSystemService (Ljava/lang/Class;)Ljava/lang/Object; keep +class android.content.pm.PackageManager keep + method <init> ()V keep +class android.text.ClipboardManager keep + method <init> ()V keep diff --git a/ravenwood/texts/services.core-ravenwood-policies.txt b/ravenwood/texts/ravenwood-services-policies.txt index d8d563e05435..5cdb4f74d7c0 100644 --- a/ravenwood/texts/services.core-ravenwood-policies.txt +++ b/ravenwood/texts/ravenwood-services-policies.txt @@ -1,7 +1,7 @@ # Ravenwood "policy" file for services.core. # Keep all AIDL interfaces -class :aidl stubclass +class :aidl keepclass # Keep all feature flag implementations -class :feature_flags stubclass +class :feature_flags keepclass diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index e66fe1b9452c..a50fb9a4c318 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -130,7 +130,7 @@ flag { flag { name: "manager_avoid_receiver_timeout" namespace: "accessibility" - description: "Avoid broadcast receiver timeout by offloading potentially slow operations to the background thread." + description: "Register receivers on background handler so they have more time to complete" bug: "333890389" metadata { purpose: PURPOSE_BUGFIX @@ -138,6 +138,16 @@ flag { } flag { + name: "manager_package_monitor_logic_fix" + namespace: "accessibility" + description: "Corrects the return values of the HandleForceStop function" + bug: "337392123" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "pinch_zoom_zero_min_span" namespace: "accessibility" description: "Whether to set min span of ScaleGestureDetector to zero." @@ -152,6 +162,16 @@ flag { } flag { + name: "remove_on_window_infos_changed_handler" + namespace: "accessibility" + description: "Updates onWindowInfosChanged() to run without posting to a handler." + bug: "333834990" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "reset_hover_event_timer_on_action_up" namespace: "accessibility" description: "Reset the timer for sending hover events on receiving ACTION_UP to guarantee the correct amount of time is available between taps." @@ -183,3 +203,10 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_color_correction_saturation" + namespace: "accessibility" + description: "Feature allows users to change color correction saturation for daltonizer." + bug: "322829049" +}
\ No newline at end of file diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 8a699ef39280..42f168bb4a6b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -44,10 +44,12 @@ import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IAccessibilityServiceConnection; import android.accessibilityservice.IBrailleDisplayController; import android.accessibilityservice.MagnificationConfig; +import android.annotation.EnforcePermission; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; +import android.annotation.PermissionManuallyEnforced; +import android.annotation.RequiresNoPermission; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -68,6 +70,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.PermissionEnforcer; import android.os.PowerManager; import android.os.RemoteCallback; import android.os.RemoteException; @@ -121,7 +124,6 @@ import java.util.Set; * This class represents an accessibility client - either an AccessibilityService or a UiAutomation. * It is responsible for behavior common to both types of clients. */ -@SuppressWarnings("MissingPermissionAnnotation") abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter, FingerprintGestureDispatcher.FingerprintGestureClient { @@ -339,6 +341,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ AccessibilityTrace trace, WindowManagerInternal windowManagerInternal, SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager a11yWindowManager) { + super(PermissionEnforcer.fromContext(context)); mContext = context; mWindowManagerService = windowManagerInternal; mId = id; @@ -469,6 +472,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return (mEventTypes != 0 && mService != null); } + @RequiresNoPermission @Override public void setOnKeyEventResult(boolean handled, int sequence) { if (svcConnTracingEnabled()) { @@ -482,6 +486,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public AccessibilityServiceInfo getServiceInfo() { if (svcConnTracingEnabled()) { @@ -501,6 +506,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ : AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) | mEventTypes; } + @RequiresNoPermission @Override public void setServiceInfo(AccessibilityServiceInfo info) { if (svcConnTracingEnabled()) { @@ -536,16 +542,19 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void setInstalledAndEnabledServices(List<AccessibilityServiceInfo> infos) { return; } + @RequiresNoPermission @Override public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() { return null; } + @RequiresNoPermission @Override public void setAttributionTag(String attributionTag) { mAttributionTag = attributionTag; @@ -558,6 +567,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ protected abstract boolean hasRightsToCurrentUserLocked(); @Nullable + @RequiresNoPermission @Override public AccessibilityWindowInfo.WindowListSparseArray getWindows() { if (svcConnTracingEnabled()) { @@ -606,6 +616,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mDisplayTypes = displayTypes; } + @RequiresNoPermission @Override public AccessibilityWindowInfo getWindow(int windowId) { if (svcConnTracingEnabled()) { @@ -646,6 +657,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId, long accessibilityNodeId, String viewIdResName, int interactionId, @@ -722,6 +734,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return null; } + @RequiresNoPermission @Override public String[] findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, @@ -797,6 +810,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return null; } + @RequiresNoPermission @Override public String[] findAccessibilityNodeInfoByAccessibilityId( int accessibilityWindowId, long accessibilityNodeId, int interactionId, @@ -875,6 +889,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return null; } + @RequiresNoPermission @Override public String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType, int interactionId, @@ -952,6 +967,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return null; } + @RequiresNoPermission @Override public String[] focusSearch(int accessibilityWindowId, long accessibilityNodeId, int direction, int interactionId, @@ -1028,6 +1044,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return null; } + @RequiresNoPermission @Override public void sendGesture(int sequence, ParceledListSlice gestureSteps) { if (svcConnTracingEnabled()) { @@ -1036,6 +1053,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) { if (svcConnTracingEnabled()) { @@ -1044,6 +1062,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId, int action, Bundle arguments, int interactionId, @@ -1075,6 +1094,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ action, arguments, interactionId, callback, mFetchFlags, interrogatingTid); } + @RequiresNoPermission @Override public boolean performGlobalAction(int action) { if (svcConnTracingEnabled()) { @@ -1093,6 +1113,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() { if (svcConnTracingEnabled()) { @@ -1111,6 +1132,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public boolean isFingerprintGestureDetectionAvailable() { if (svcConnTracingEnabled()) { @@ -1133,6 +1155,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Nullable + @RequiresNoPermission @Override public MagnificationConfig getMagnificationConfig(int displayId) { if (svcConnTracingEnabled()) { @@ -1151,6 +1174,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public float getMagnificationScale(int displayId) { if (svcConnTracingEnabled()) { @@ -1169,6 +1193,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public Region getMagnificationRegion(int displayId) { if (svcConnTracingEnabled()) { @@ -1193,6 +1218,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } + @RequiresNoPermission @Override public Region getCurrentMagnificationRegion(int displayId) { if (svcConnTracingEnabled()) { @@ -1216,6 +1242,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public float getMagnificationCenterX(int displayId) { if (svcConnTracingEnabled()) { @@ -1237,6 +1264,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public float getMagnificationCenterY(int displayId) { if (svcConnTracingEnabled()) { @@ -1258,6 +1286,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public boolean resetMagnification(int displayId, boolean animate) { if (svcConnTracingEnabled()) { @@ -1282,6 +1311,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public boolean resetCurrentMagnification(int displayId, boolean animate) { if (svcConnTracingEnabled()) { @@ -1307,6 +1337,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public boolean setMagnificationConfig(int displayId, @NonNull MagnificationConfig config, boolean animate) { @@ -1333,6 +1364,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void setMagnificationCallbackEnabled(int displayId, boolean enabled) { if (svcConnTracingEnabled()) { @@ -1351,6 +1383,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return mInvocationHandler.isMagnificationCallbackEnabled(displayId); } + @RequiresNoPermission @Override public void setSoftKeyboardCallbackEnabled(boolean enabled) { if (svcConnTracingEnabled()) { @@ -1364,6 +1397,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void takeScreenshotOfWindow(int accessibilityWindowId, int interactionId, ScreenCapture.ScreenCaptureListener listener, @@ -1414,6 +1448,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void takeScreenshot(int displayId, RemoteCallback callback) { if (svcConnTracingEnabled()) { @@ -1553,6 +1588,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Override + @PermissionManuallyEnforced public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; synchronized (mLock) { @@ -1649,6 +1685,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ * @param displayId The id of the logical display that was added. * @return window token. */ + @RequiresNoPermission @Override public IBinder getOverlayWindowToken(int displayId) { if (svcConnTracingEnabled()) { @@ -1670,6 +1707,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ * @param token The token * @return window id */ + @RequiresNoPermission @Override public int getWindowIdForLeashToken(@NonNull IBinder token) { if (svcConnTracingEnabled()) { @@ -2523,6 +2561,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return mSendMotionEvents; } + @RequiresNoPermission @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) { if (svcConnTracingEnabled()) { @@ -2537,6 +2576,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void setTouchExplorationPassthroughRegion(int displayId, Region region) { if (svcConnTracingEnabled()) { @@ -2551,6 +2591,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void setFocusAppearance(int strokeWidth, int color) { if (svcConnTracingEnabled()) { @@ -2558,6 +2599,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void setCacheEnabled(boolean enabled) { if (svcConnTracingEnabled()) { @@ -2574,6 +2616,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void logTrace(long timestamp, String where, long loggingTypes, String callingParams, int processId, long threadId, int callingUid, Bundle callingStack) { @@ -2629,6 +2672,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mTrace.logTrace(TRACE_WM + "." + methodName, FLAGS_WINDOW_MANAGER_INTERNAL, params); } + @RequiresNoPermission @Override public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) { final long identity = Binder.clearCallingIdentity(); @@ -2647,6 +2691,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ return false; } + @RequiresNoPermission @Override public void requestTouchExploration(int displayId) { final long identity = Binder.clearCallingIdentity(); @@ -2657,6 +2702,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void requestDragging(int displayId, int pointerId) { final long identity = Binder.clearCallingIdentity(); @@ -2667,6 +2713,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void requestDelegating(int displayId) { final long identity = Binder.clearCallingIdentity(); @@ -2677,6 +2724,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void onDoubleTap(int displayId) { final long identity = Binder.clearCallingIdentity(); @@ -2687,6 +2735,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void onDoubleTapAndHold(int displayId) { final long identity = Binder.clearCallingIdentity(); @@ -2700,6 +2749,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ /** * Sets the scaling factor for animations. */ + @RequiresNoPermission @Override public void setAnimationScale(float scale) { final long identity = Binder.clearCallingIdentity(); @@ -2717,6 +2767,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void attachAccessibilityOverlayToDisplay( int interactionId, @@ -2733,6 +2784,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @RequiresNoPermission @Override public void attachAccessibilityOverlayToWindow( int interactionId, @@ -2779,12 +2831,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Override - @SuppressLint("AndroidFrameworkRequiresPermission") // Unsupported in Abstract class + @EnforcePermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void connectBluetoothBrailleDisplay(String bluetoothAddress, IBrailleDisplayController controller) { + connectBluetoothBrailleDisplay_enforcePermission(); throw new UnsupportedOperationException(); } + @RequiresNoPermission @Override public void connectUsbBrailleDisplay(UsbDevice usbDevice, IBrailleDisplayController controller) { @@ -2792,8 +2846,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Override - @SuppressLint("AndroidFrameworkRequiresPermission") // Unsupported in Abstract class + @EnforcePermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) { + setTestBrailleDisplayData_enforcePermission(); throw new UnsupportedOperationException(); } } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 2bece6cac95a..d7ed867c2ae5 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -697,7 +697,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /** * Returns the lock object for any synchronized test blocks. - * Should not be used outside of testing. + * External classes should only use for testing. * @return lock object. */ @VisibleForTesting @@ -801,7 +801,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * * @param packages list of packages that have stopped. * @param userState user state to be read & modified. - * @return {@code true} if a service was enabled or a button target was removed, + * @return {@code true} if the lists of enabled services or buttons were changed, * {@code false} otherwise. */ @VisibleForTesting @@ -824,6 +824,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub userState.getBindingServicesLocked().remove(comp); userState.getCrashedServicesLocked().remove(comp); enabledServicesChanged = true; + break; } } } @@ -851,132 +852,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return mPackageMonitor; } - private void registerBroadcastReceivers() { - mPackageMonitor = new PackageMonitor(true) { - @Override - public void onSomePackagesChanged() { - if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged", - FLAGS_PACKAGE_BROADCAST_RECEIVER); - } - - final int userId = getChangingUserId(); - List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; - List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; - parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); - parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); - synchronized (mLock) { - // Only the profile parent can install accessibility services. - // Therefore we ignore packages from linked profiles. - if (userId != mCurrentUserId) { - return; - } - onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, - parsedAccessibilityShortcutInfos); - } - } - - @Override - public void onPackageUpdateFinished(String packageName, int uid) { - // The package should already be removed from mBoundServices, and added into - // mBindingServices in binderDied() during updating. Remove services from this - // package from mBindingServices, and then update the user state to re-bind new - // versions of them. - if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".PM.onPackageUpdateFinished", - FLAGS_PACKAGE_BROADCAST_RECEIVER, - "packageName=" + packageName + ";uid=" + uid); - } - final int userId = getChangingUserId(); - List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = null; - List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = null; - parsedAccessibilityServiceInfos = parseAccessibilityServiceInfos(userId); - parsedAccessibilityShortcutInfos = parseAccessibilityShortcutInfos(userId); - synchronized (mLock) { - if (userId != mCurrentUserId) { - return; - } - final AccessibilityUserState userState = getUserStateLocked(userId); - final boolean reboundAService = userState.getBindingServicesLocked().removeIf( - component -> component != null - && component.getPackageName().equals(packageName)) - || userState.mCrashedServices.removeIf(component -> component != null - && component.getPackageName().equals(packageName)); - // Reloads the installed services info to make sure the rebound service could - // get a new one. - userState.mInstalledServices.clear(); - final boolean configurationChanged; - configurationChanged = readConfigurationForUserStateLocked(userState, - parsedAccessibilityServiceInfos, parsedAccessibilityShortcutInfos); - if (reboundAService || configurationChanged) { - onUserStateChangedLocked(userState); - } - // Passing 0 for restoreFromSdkInt to have this migration check execute each - // time. It can make sure a11y button settings are correctly if there's an a11y - // service updated and modifies the a11y button configuration. - migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName, - /* restoreFromSdkInt = */0); - } - } - - @Override - public void onPackageRemoved(String packageName, int uid) { - if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved", - FLAGS_PACKAGE_BROADCAST_RECEIVER, - "packageName=" + packageName + ";uid=" + uid); - } - - synchronized (mLock) { - final int userId = getChangingUserId(); - // Only the profile parent can install accessibility services. - // Therefore we ignore packages from linked profiles. - if (userId != mCurrentUserId) { - return; - } - onPackageRemovedLocked(packageName); - } - } - - /** - * Handles instances in which a package or packages have forcibly stopped. - * - * @param intent intent containing package event information. - * @param uid linux process user id (different from Android user id). - * @param packages array of package names that have stopped. - * @param doit whether to try and handle the stop or just log the trace. - * - * @return {@code true} if package should be restarted, {@code false} otherwise. - */ - @Override - public boolean onHandleForceStop(Intent intent, String[] packages, - int uid, boolean doit) { - if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_PACKAGE_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop", - FLAGS_PACKAGE_BROADCAST_RECEIVER, - "intent=" + intent + ";packages=" + Arrays.toString(packages) - + ";uid=" + uid + ";doit=" + doit); - } - synchronized (mLock) { - final int userId = getChangingUserId(); - // Only the profile parent can install accessibility services. - // Therefore we ignore packages from linked profiles. - if (userId != mCurrentUserId) { - return false; - } - final AccessibilityUserState userState = getUserStateLocked(userId); - - if (doit && onPackagesForceStoppedLocked(packages, userState)) { - onUserStateChangedLocked(userState); - return false; - } else { - return true; - } - } - } - }; + @VisibleForTesting + void setPackageMonitor(PackageMonitor monitor) { + mPackageMonitor = monitor; + } + private void registerBroadcastReceivers() { // package changes + mPackageMonitor = new ManagerPackageMonitor(this); mPackageMonitor.register(mContext, null, UserHandle.ALL, true); // user change and unlock @@ -986,21 +869,58 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_SETTING_RESTORED); + Handler receiverHandler = + Flags.managerAvoidReceiverTimeout() ? BackgroundThread.getHandler() : null; mContext.registerReceiverAsUser(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_USER_BROADCAST_RECEIVER)) { - mTraceManager.logTrace(LOG_TAG + ".BR.onReceive", FLAGS_USER_BROADCAST_RECEIVER, + mTraceManager.logTrace( + LOG_TAG + ".BR.onReceive", + FLAGS_USER_BROADCAST_RECEIVER, "context=" + context + ";intent=" + intent); } - if (com.android.server.accessibility.Flags.managerAvoidReceiverTimeout()) { - BackgroundThread.getHandler().post(() -> processBroadcast(intent)); - } else { - processBroadcast(intent); + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { + unlockUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { + final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); + if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) { + synchronized (mLock) { + restoreEnabledAccessibilityServicesLocked( + intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE), + intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE), + intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, + 0)); + } + } else if (ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED.equals(which)) { + synchronized (mLock) { + restoreLegacyDisplayMagnificationNavBarIfNeededLocked( + intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE), + intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, + 0)); + } + } else if (Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS.equals(which)) { + synchronized (mLock) { + restoreAccessibilityButtonTargetsLocked( + intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE), + intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)); + } + } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(which)) { + if (!android.view.accessibility.Flags.a11yQsShortcut()) { + return; + } + restoreAccessibilityQsTargets( + intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)); + } } } - }, UserHandle.ALL, intentFilter, null, null); + }, UserHandle.ALL, intentFilter, null, receiverHandler); final IntentFilter filter = new IntentFilter(); filter.addAction(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED); @@ -1010,7 +930,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub setNonA11yToolNotificationToMatchSafetyCenter(); } }; - mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, mMainHandler, + mContext.registerReceiverAsUser( + receiver, UserHandle.ALL, filter, null, mMainHandler, Context.RECEIVER_EXPORTED); if (!android.companion.virtual.flags.Flags.vdmPublicApis()) { @@ -2000,19 +1921,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mA11yWindowManager.onTouchInteractionEnd(); } - private void processBroadcast(Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_USER_SWITCHED.equals(action)) { - switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { - unlockUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - } else if (Intent.ACTION_USER_REMOVED.equals(action)) { - removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); - } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) { - restoreSetting(intent); - } - } - @VisibleForTesting void switchUser(int userId) { mMagnificationController.updateUserIdIfNeeded(userId); @@ -2105,38 +2013,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub getMagnificationController().onUserRemoved(userId); } - private void restoreSetting(Intent intent) { - final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME); - if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) { - synchronized (mLock) { - restoreEnabledAccessibilityServicesLocked( - intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE), - intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE), - intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, - 0)); - } - } else if (ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED.equals(which)) { - synchronized (mLock) { - restoreLegacyDisplayMagnificationNavBarIfNeededLocked( - intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE), - intent.getIntExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, - 0)); - } - } else if (Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS.equals(which)) { - synchronized (mLock) { - restoreAccessibilityButtonTargetsLocked( - intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE), - intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)); - } - } else if (Settings.Secure.ACCESSIBILITY_QS_TARGETS.equals(which)) { - if (!android.view.accessibility.Flags.a11yQsShortcut()) { - return; - } - restoreAccessibilityQsTargets( - intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)); - } - } - // Called only during settings restore; currently supports only the owner user // TODO: http://b/22388012 void restoreEnabledAccessibilityServicesLocked(String oldSetting, String newSetting, @@ -4381,7 +4257,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub ); if (!targetWithNoTile.isEmpty()) { - throw new IllegalArgumentException( + Slog.e(LOG_TAG, "Unable to add/remove Tiles for a11y features: " + targetWithNoTile + "as the Tiles aren't provided"); } @@ -6233,6 +6109,169 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + @VisibleForTesting + public static class ManagerPackageMonitor extends PackageMonitor { + private final AccessibilityManagerService mManagerService; + public ManagerPackageMonitor(AccessibilityManagerService managerService) { + super(/* supportsPackageRestartQuery = */ true); + mManagerService = managerService; + } + + @Override + public void onSomePackagesChanged() { + if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onSomePackagesChanged", + FLAGS_PACKAGE_BROADCAST_RECEIVER); + } + + final int userId = getChangingUserId(); + List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = mManagerService + .parseAccessibilityServiceInfos(userId); + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = mManagerService + .parseAccessibilityShortcutInfos(userId); + synchronized (mManagerService.getLock()) { + // Only the profile parent can install accessibility services. + // Therefore we ignore packages from linked profiles. + if (userId != mManagerService.getCurrentUserIdLocked()) { + return; + } + mManagerService.onSomePackagesChangedLocked(parsedAccessibilityServiceInfos, + parsedAccessibilityShortcutInfos); + } + } + + @Override + public void onPackageUpdateFinished(String packageName, int uid) { + // The package should already be removed from mBoundServices, and added into + // mBindingServices in binderDied() during updating. Remove services from this + // package from mBindingServices, and then update the user state to re-bind new + // versions of them. + if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mManagerService.mTraceManager.logTrace( + LOG_TAG + ".PM.onPackageUpdateFinished", + FLAGS_PACKAGE_BROADCAST_RECEIVER, + "packageName=" + packageName + ";uid=" + uid); + } + final int userId = getChangingUserId(); + List<AccessibilityServiceInfo> parsedAccessibilityServiceInfos = mManagerService + .parseAccessibilityServiceInfos(userId); + List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos = + mManagerService.parseAccessibilityShortcutInfos(userId); + synchronized (mManagerService.getLock()) { + if (userId != mManagerService.getCurrentUserIdLocked()) { + return; + } + final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId); + final boolean reboundAService = userState.getBindingServicesLocked().removeIf( + component -> component != null + && component.getPackageName().equals(packageName)) + || userState.mCrashedServices.removeIf(component -> component != null + && component.getPackageName().equals(packageName)); + // Reloads the installed services info to make sure the rebound service could + // get a new one. + userState.mInstalledServices.clear(); + final boolean configurationChanged; + configurationChanged = mManagerService.readConfigurationForUserStateLocked( + userState, parsedAccessibilityServiceInfos, + parsedAccessibilityShortcutInfos); + if (reboundAService || configurationChanged) { + mManagerService.onUserStateChangedLocked(userState); + } + // Passing 0 for restoreFromSdkInt to have this migration check execute each + // time. It can make sure a11y button settings are correctly if there's an a11y + // service updated and modifies the a11y button configuration. + mManagerService.migrateAccessibilityButtonSettingsIfNecessaryLocked( + userState, packageName, /* restoreFromSdkInt = */0); + } + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onPackageRemoved", + FLAGS_PACKAGE_BROADCAST_RECEIVER, + "packageName=" + packageName + ";uid=" + uid); + } + + synchronized (mManagerService.getLock()) { + final int userId = getChangingUserId(); + // Only the profile parent can install accessibility services. + // Therefore we ignore packages from linked profiles. + if (userId != mManagerService.getCurrentUserIdLocked()) { + return; + } + mManagerService.onPackageRemovedLocked(packageName); + } + } + + /** + * Handles instances in which a package or packages have forcibly stopped. + * + * @param intent intent containing package event information. + * @param uid linux process user id (different from Android user id). + * @param packages array of package names that have stopped. + * @param doit whether to try and handle the stop or just log the trace. + * + * @return {@code true} if doit == {@code false} + * and at least one of the provided packages is enabled. + * In any other case, returns {@code false}. + * This is to indicate whether further action is necessary. + */ + @Override + public boolean onHandleForceStop(Intent intent, String[] packages, + int uid, boolean doit) { + if (mManagerService.mTraceManager.isA11yTracingEnabledForTypes( + FLAGS_PACKAGE_BROADCAST_RECEIVER)) { + mManagerService.mTraceManager.logTrace(LOG_TAG + ".PM.onHandleForceStop", + FLAGS_PACKAGE_BROADCAST_RECEIVER, + "intent=" + intent + ";packages=" + Arrays.toString(packages) + + ";uid=" + uid + ";doit=" + doit); + } + synchronized (mManagerService.getLock()) { + final int userId = getChangingUserId(); + // Only the profile parent can install accessibility services. + // Therefore we ignore packages from linked profiles. + if (userId != mManagerService.getCurrentUserIdLocked()) { + return false; + } + final AccessibilityUserState userState = mManagerService.getUserStateLocked(userId); + + if (Flags.managerPackageMonitorLogicFix()) { + if (!doit) { + // if we're not handling the stop here, then we only need to know + // if any of the force-stopped packages are currently enabled. + return userState.mEnabledServices.stream().anyMatch( + (comp) -> Arrays.stream(packages).anyMatch( + (pkg) -> pkg.equals(comp.getPackageName())) + ); + } else if (mManagerService.onPackagesForceStoppedLocked(packages, userState)) { + mManagerService.onUserStateChangedLocked(userState); + } + return false; + } else { + // this old logic did not properly indicate when base packageMonitor's routine + // should handle stopping the package. + if (doit && mManagerService.onPackagesForceStoppedLocked(packages, userState)) { + mManagerService.onUserStateChangedLocked(userState); + return false; + } else { + return true; + } + } + } + } + + @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + // We care about all package changes, not just the whole package itself which is + // default behavior. + return true; + } + } + void sendPendingWindowStateChangedEventsForAvailableWindowLocked(int windowId) { final int eventSize = mSendWindowStateChangedEventRunnables.size(); for (int i = eventSize - 1; i >= 0; i--) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index 1f65e15c1bff..786d167af5de 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java @@ -30,10 +30,10 @@ import android.accessibilityservice.BrailleDisplayController; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.IBrailleDisplayController; import android.accessibilityservice.TouchInteractionController; +import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.RequiresPermission; -import android.annotation.SuppressLint; +import android.annotation.RequiresNoPermission; import android.annotation.UserIdInt; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; @@ -79,7 +79,6 @@ import java.util.Set; * passed to the service it represents as soon it is bound. It also serves as the * connection for the service. */ -@SuppressWarnings("MissingPermissionAnnotation") class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection { private static final String LOG_TAG = "AccessibilityServiceConnection"; @@ -110,6 +109,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect mUserId = userId; } + @RequiresNoPermission @Override public void sessionCreated(IAccessibilityInputMethodSession session, int id) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "ASC.sessionCreated"); @@ -196,6 +196,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect return mSecurityPolicy.canRetrieveWindowContentLocked(this) && mRetrieveInteractiveWindows; } + @RequiresNoPermission @Override public void disableSelf() { if (svcConnTracingEnabled()) { @@ -253,6 +254,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } + @RequiresNoPermission @Override public AccessibilityServiceInfo getServiceInfo() { return mAccessibilityServiceInfo; @@ -330,6 +332,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect return false; } + @RequiresNoPermission @Override public boolean setSoftKeyboardShowMode(int showMode) { if (svcConnTracingEnabled()) { @@ -351,6 +354,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } + @RequiresNoPermission @Override public int getSoftKeyboardShowMode() { if (svcConnTracingEnabled()) { @@ -365,6 +369,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } + @RequiresNoPermission @Override public boolean switchToInputMethod(String imeId) { if (svcConnTracingEnabled()) { @@ -386,6 +391,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect return result; } + @RequiresNoPermission @Override @AccessibilityService.SoftKeyboardController.EnableImeResult public int setInputMethodEnabled(String imeId, boolean enabled) throws SecurityException { @@ -421,6 +427,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect return ENABLE_IME_FAIL_UNKNOWN; } + @RequiresNoPermission @Override public boolean isAccessibilityButtonAvailable() { if (svcConnTracingEnabled()) { @@ -535,6 +542,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } + @RequiresNoPermission @Override public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) { synchronized (mLock) { @@ -569,6 +577,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } + @RequiresNoPermission @Override public void setFocusAppearance(int strokeWidth, int color) { AccessibilityUserState userState = mUserStateWeakReference.get(); @@ -683,16 +692,15 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect * {@link android.bluetooth.BluetoothDevice#getAddress()}. */ @Override - @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + @EnforcePermission(Manifest.permission.BLUETOOTH_CONNECT) public void connectBluetoothBrailleDisplay( @NonNull String bluetoothAddress, @NonNull IBrailleDisplayController controller) { + connectBluetoothBrailleDisplay_enforcePermission(); if (!android.view.accessibility.Flags.brailleDisplayHid()) { throw new IllegalStateException("Flag BRAILLE_DISPLAY_HID not enabled"); } Objects.requireNonNull(bluetoothAddress); Objects.requireNonNull(controller); - mContext.enforceCallingPermission(Manifest.permission.BLUETOOTH_CONNECT, - "Missing BLUETOOTH_CONNECT permission"); if (!BluetoothAdapter.checkBluetoothAddress(bluetoothAddress)) { throw new IllegalArgumentException( bluetoothAddress + " is not a valid Bluetooth address"); @@ -728,7 +736,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect * * <p>The caller package must already have USB permission for this {@link UsbDevice}. */ - @SuppressLint("MissingPermission") // system_server has the required MANAGE_USB permission + @RequiresNoPermission @Override @NonNull public void connectUsbBrailleDisplay(@NonNull UsbDevice usbDevice, @@ -783,11 +791,10 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } @Override - @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY) + @EnforcePermission(Manifest.permission.MANAGE_ACCESSIBILITY) public void setTestBrailleDisplayData(List<Bundle> brailleDisplays) { + setTestBrailleDisplayData_enforcePermission(); // Enforce that this TestApi is only called by trusted (test) callers. - mContext.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY, - "Missing MANAGE_ACCESSIBILITY permission"); mTestBrailleDisplays = brailleDisplays; } diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java index b77b2bef57a2..4cb3d247edb0 100644 --- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java @@ -24,6 +24,8 @@ import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.accessibilityservice.MagnificationConfig; import android.annotation.NonNull; +import android.annotation.PermissionManuallyEnforced; +import android.annotation.RequiresNoPermission; import android.content.ComponentName; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -64,7 +66,6 @@ import java.util.Set; * * TODO(241429275): Initialize this when a proxy is registered. */ -@SuppressWarnings("MissingPermissionAnnotation") public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection { private static final String LOG_TAG = "ProxyAccessibilityServiceConnection"; @@ -125,6 +126,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon * * @param infos the list of enabled and installed services. */ + @RequiresNoPermission @Override public void setInstalledAndEnabledServices(@NonNull List<AccessibilityServiceInfo> infos) { final long identity = Binder.clearCallingIdentity(); @@ -215,6 +217,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } } + @RequiresNoPermission @Override @NonNull public List<AccessibilityServiceInfo> getInstalledAndEnabledServices() { @@ -224,6 +227,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } } + @RequiresNoPermission @Override public AccessibilityWindowInfo.WindowListSparseArray getWindows() { final AccessibilityWindowInfo.WindowListSparseArray allWindows = super.getWindows(); @@ -235,6 +239,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon return displayWindows; } + @RequiresNoPermission @Override public void setFocusAppearance(int strokeWidth, int color) { synchronized (mLock) { @@ -272,6 +277,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon return mFocusColor; } + @RequiresNoPermission @Override int resolveAccessibilityWindowIdForFindFocusLocked(int windowId, int focusType) { if (windowId == AccessibilityWindowInfo.ANY_WINDOW_ID) { @@ -314,12 +320,14 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */ + @RequiresNoPermission @Override public boolean isCapturingFingerprintGestures() throws UnsupportedOperationException { throw new UnsupportedOperationException("isCapturingFingerprintGestures is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */ + @RequiresNoPermission @Override public void onFingerprintGestureDetectionActiveChanged(boolean active) throws UnsupportedOperationException { @@ -328,12 +336,14 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */ + @RequiresNoPermission @Override public void onFingerprintGesture(int gesture) throws UnsupportedOperationException { throw new UnsupportedOperationException("onFingerprintGesture is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need fingerprint hardware */ + @RequiresNoPermission @Override public boolean isFingerprintGestureDetectionAvailable() throws UnsupportedOperationException { throw new UnsupportedOperationException("isFingerprintGestureDetectionAvailable is not" @@ -341,6 +351,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy is not a Service */ + @RequiresNoPermission @Override public void onServiceConnected(ComponentName name, IBinder service) throws UnsupportedOperationException { @@ -349,6 +360,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy is not a Service */ + @RequiresNoPermission @Override public void onServiceDisconnected(ComponentName name) throws UnsupportedOperationException { @@ -357,6 +369,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon /** @throws UnsupportedOperationException since a proxy should use * setInstalledAndEnabledServices*/ + @RequiresNoPermission @Override public void setServiceInfo(AccessibilityServiceInfo info) throws UnsupportedOperationException { @@ -365,6 +378,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy should use A11yManager#unregister */ + @RequiresNoPermission @Override public void disableSelf() throws UnsupportedOperationException { // A proxy uses A11yManager#unregister to turn itself off. @@ -372,12 +386,14 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not have global system access */ + @RequiresNoPermission @Override public boolean performGlobalAction(int action) throws UnsupportedOperationException { throw new UnsupportedOperationException("performGlobalAction is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need key events */ + @RequiresNoPermission @Override public void setOnKeyEventResult(boolean handled, int sequence) throws UnsupportedOperationException { @@ -385,6 +401,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not have global system access */ + @RequiresNoPermission @Override public @NonNull List<AccessibilityNodeInfo.AccessibilityAction> getSystemActions() throws UnsupportedOperationException { @@ -393,6 +410,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon /** @throws UnsupportedOperationException since a proxy does not need magnification */ @Nullable + @RequiresNoPermission @Override public MagnificationConfig getMagnificationConfig(int displayId) throws UnsupportedOperationException { @@ -400,30 +418,35 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public float getMagnificationScale(int displayId) throws UnsupportedOperationException { throw new UnsupportedOperationException("getMagnificationScale is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public float getMagnificationCenterX(int displayId) throws UnsupportedOperationException { throw new UnsupportedOperationException("getMagnificationCenterX is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public float getMagnificationCenterY(int displayId) throws UnsupportedOperationException { throw new UnsupportedOperationException("getMagnificationCenterY is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public Region getMagnificationRegion(int displayId) throws UnsupportedOperationException { throw new UnsupportedOperationException("getMagnificationRegion is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public Region getCurrentMagnificationRegion(int displayId) throws UnsupportedOperationException { @@ -431,6 +454,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public boolean resetMagnification(int displayId, boolean animate) throws UnsupportedOperationException { @@ -438,6 +462,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public boolean resetCurrentMagnification(int displayId, boolean animate) throws UnsupportedOperationException { @@ -445,6 +470,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public boolean setMagnificationConfig(int displayId, @androidx.annotation.NonNull MagnificationConfig config, boolean animate) @@ -453,6 +479,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public void setMagnificationCallbackEnabled(int displayId, boolean enabled) throws UnsupportedOperationException { @@ -460,24 +487,28 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need magnification */ + @RequiresNoPermission @Override public boolean isMagnificationCallbackEnabled(int displayId) { throw new UnsupportedOperationException("isMagnificationCallbackEnabled is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need IME access*/ + @RequiresNoPermission @Override public boolean setSoftKeyboardShowMode(int showMode) throws UnsupportedOperationException { throw new UnsupportedOperationException("setSoftKeyboardShowMode is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need IME access */ + @RequiresNoPermission @Override public int getSoftKeyboardShowMode() throws UnsupportedOperationException { throw new UnsupportedOperationException("getSoftKeyboardShowMode is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need IME access */ + @RequiresNoPermission @Override public void setSoftKeyboardCallbackEnabled(boolean enabled) throws UnsupportedOperationException { @@ -485,12 +516,14 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need IME access */ + @RequiresNoPermission @Override public boolean switchToInputMethod(String imeId) throws UnsupportedOperationException { throw new UnsupportedOperationException("switchToInputMethod is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need IME access */ + @RequiresNoPermission @Override public int setInputMethodEnabled(String imeId, boolean enabled) throws UnsupportedOperationException { @@ -498,12 +531,14 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need access to the shortcut */ + @RequiresNoPermission @Override public boolean isAccessibilityButtonAvailable() throws UnsupportedOperationException { throw new UnsupportedOperationException("isAccessibilityButtonAvailable is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */ + @RequiresNoPermission @Override public void sendGesture(int sequence, ParceledListSlice gestureSteps) throws UnsupportedOperationException { @@ -511,6 +546,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */ + @RequiresNoPermission @Override public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) throws UnsupportedOperationException { @@ -518,6 +554,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need access to screenshots */ + @RequiresNoPermission @Override public void takeScreenshot(int displayId, RemoteCallback callback) throws UnsupportedOperationException { @@ -525,6 +562,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */ + @RequiresNoPermission @Override public void setGestureDetectionPassthroughRegion(int displayId, Region region) throws UnsupportedOperationException { @@ -533,6 +571,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */ + @RequiresNoPermission @Override public void setTouchExplorationPassthroughRegion(int displayId, Region region) throws UnsupportedOperationException { @@ -541,6 +580,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need gestures/input access */ + @RequiresNoPermission @Override public void setServiceDetectsGesturesEnabled(int displayId, boolean mode) throws UnsupportedOperationException { @@ -549,36 +589,42 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon } /** @throws UnsupportedOperationException since a proxy does not need touch input access */ + @RequiresNoPermission @Override public void requestTouchExploration(int displayId) throws UnsupportedOperationException { throw new UnsupportedOperationException("requestTouchExploration is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need touch input access */ + @RequiresNoPermission @Override public void requestDragging(int displayId, int pointerId) throws UnsupportedOperationException { throw new UnsupportedOperationException("requestDragging is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need touch input access */ + @RequiresNoPermission @Override public void requestDelegating(int displayId) throws UnsupportedOperationException { throw new UnsupportedOperationException("requestDelegating is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need touch input access */ + @RequiresNoPermission @Override public void onDoubleTap(int displayId) throws UnsupportedOperationException { throw new UnsupportedOperationException("onDoubleTap is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need touch input access */ + @RequiresNoPermission @Override public void onDoubleTapAndHold(int displayId) throws UnsupportedOperationException { throw new UnsupportedOperationException("onDoubleTapAndHold is not supported"); } /** @throws UnsupportedOperationException since a proxy does not need touch input access */ + @RequiresNoPermission @Override public void setAnimationScale(float scale) throws UnsupportedOperationException { throw new UnsupportedOperationException("setAnimationScale is not supported"); @@ -615,6 +661,7 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon return updated; } + @PermissionManuallyEnforced @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java index aad9e24ee8cc..63a183d506f8 100644 --- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java +++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java @@ -21,6 +21,8 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityTrace; import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.Nullable; +import android.annotation.PermissionManuallyEnforced; +import android.annotation.RequiresNoPermission; import android.app.UiAutomation; import android.content.ComponentName; import android.content.Context; @@ -242,7 +244,6 @@ class UiAutomationManager { } } - @SuppressWarnings("MissingPermissionAnnotation") private class UiAutomationService extends AbstractAccessibilityServiceConnection { private final Handler mMainHandler; @@ -318,6 +319,7 @@ class UiAutomationManager { return true; } + @PermissionManuallyEnforced @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return; @@ -330,31 +332,37 @@ class UiAutomationManager { } // Since this isn't really an accessibility service, several methods are just stubbed here. + @RequiresNoPermission @Override public boolean setSoftKeyboardShowMode(int mode) { return false; } + @RequiresNoPermission @Override public int getSoftKeyboardShowMode() { return 0; } + @RequiresNoPermission @Override public boolean switchToInputMethod(String imeId) { return false; } + @RequiresNoPermission @Override public int setInputMethodEnabled(String imeId, boolean enabled) { return AccessibilityService.SoftKeyboardController.ENABLE_IME_FAIL_UNKNOWN; } + @RequiresNoPermission @Override public boolean isAccessibilityButtonAvailable() { return false; } + @RequiresNoPermission @Override public void disableSelf() {} @@ -375,6 +383,7 @@ class UiAutomationManager { @Override public void onFingerprintGesture(int gesture) {} + @RequiresNoPermission @Override public void takeScreenshot(int displayId, RemoteCallback callback) {} } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java index 2f54f8c8bdb5..04b42e49fad9 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java @@ -974,6 +974,10 @@ public class TouchExplorer extends BaseEventStreamTransformation clear(event, policyFlags); return; case ACTION_POINTER_DOWN: + if (mDraggingPointerId != INVALID_POINTER_ID) { + mDispatcher.sendMotionEvent( + event, ACTION_UP, rawEvent, pointerIdBits, policyFlags); + } if (mState.isServiceDetectingGestures()) { mAms.sendMotionEventToListeningServices(rawEvent); return; @@ -981,10 +985,6 @@ public class TouchExplorer extends BaseEventStreamTransformation // We are in dragging state so we have two pointers and another one // goes down => delegate the three pointers to the view hierarchy mState.startDelegating(); - if (mDraggingPointerId != INVALID_POINTER_ID) { - mDispatcher.sendMotionEvent( - event, ACTION_UP, rawEvent, pointerIdBits, policyFlags); - } mDispatcher.sendDownForAllNotInjectedPointers(event, policyFlags); break; case ACTION_MOVE: @@ -1469,10 +1469,7 @@ public class TouchExplorer extends BaseEventStreamTransformation int policyFlags = mState.getLastReceivedPolicyFlags(); if (mState.isDragging()) { // Send an event to the end of the drag gesture. - int pointerIdBits = ALL_POINTER_ID_BITS; - if (Flags.fixDragPointerWhenEndingDrag()) { - pointerIdBits = 1 << mDraggingPointerId; - } + int pointerIdBits = 1 << mDraggingPointerId; mDispatcher.sendMotionEvent(event, ACTION_UP, rawEvent, pointerIdBits, policyFlags); } mState.startDelegating(); diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index e830523cba60..55677078f939 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; @@ -171,7 +172,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku OnCrossProfileWidgetProvidersChangeListener { private static final String TAG = "AppWidgetServiceImpl"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Build.IS_DEBUGGABLE; private static final String OLD_KEYGUARD_HOST_PACKAGE = "android"; private static final String NEW_KEYGUARD_HOST_PACKAGE = "com.android.keyguard"; @@ -520,9 +521,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku false); final boolean packageRemovedPermanently = (extras == null || !isReplacing || (isReplacing && isArchival)); - if (packageRemovedPermanently) { for (String pkgName : pkgList) { + if (DEBUG) { + Slog.i(TAG, "calling removeHostsAndProvidersForPackageLocked() " + + "because package removed permanently. extras=" + extras + + " isReplacing=" + isReplacing + " isArchival=" + isArchival); + } componentsModified |= removeHostsAndProvidersForPackageLocked( pkgName, userId); } @@ -736,7 +741,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 +758,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 +767,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 +786,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); @@ -1568,9 +1576,36 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Binder.getCallingUid(), callingPackage); if (widget != null && widget.provider != null && !widget.provider.zombie) { - return cloneIfLocalBinder(widget.provider.getInfoLocked(mContext)); + final AppWidgetProviderInfo info = widget.provider.getInfoLocked(mContext); + if (info == null) { + Slog.e(TAG, "getAppWidgetInfo() returns null because" + + " widget.provider.getInfoLocked() returned null." + + " appWidgetId=" + appWidgetId + " userId=" + userId + + " widget=" + widget); + return null; + } + final AppWidgetProviderInfo ret = cloneIfLocalBinder(info); + if (ret == null) { + Slog.e(TAG, "getAppWidgetInfo() returns null because" + + " cloneIfLocalBinder() returned null." + + " appWidgetId=" + appWidgetId + " userId=" + userId + + " widget=" + widget + " appWidgetProviderInfo=" + info); + } + return ret; + } else { + if (widget == null) { + Slog.e(TAG, "getAppWidgetInfo() returns null because widget is null." + + " appWidgetId=" + appWidgetId + " userId=" + userId); + } else if (widget.provider == null) { + Slog.e(TAG, "getAppWidgetInfo() returns null because widget.provider is null." + + " appWidgetId=" + appWidgetId + " userId=" + userId + + " widget=" + widget); + } else { + Slog.e(TAG, "getAppWidgetInfo() returns null because widget.provider is zombie." + + " appWidgetId=" + appWidgetId + " userId=" + userId + + " widget=" + widget); + } } - return null; } } @@ -2022,6 +2057,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void deleteHostLocked(Host host) { + if (DEBUG) { + Slog.i(TAG, "deleteHostLocked() " + host); + } final int N = host.widgets.size(); for (int i = N - 1; i >= 0; i--) { Widget widget = host.widgets.remove(i); @@ -2034,6 +2072,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void deleteAppWidgetLocked(Widget widget) { + if (DEBUG) { + Slog.i(TAG, "deleteAppWidgetLocked() " + widget); + } // We first unbind all services that are bound to this id // Check if we need to destroy any services (if no other app widgets are // referencing the same service) @@ -2501,6 +2542,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku return widget; } } + if (DEBUG) { + Slog.i(TAG, "cannot find widget for appWidgetId=" + appWidgetId + " uid=" + uid + + " packageName=" + packageName); + } return null; } @@ -2618,6 +2663,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Remove widgets for provider that are hosted in userId. private void deleteWidgetsLocked(Provider provider, int userId) { + if (DEBUG) { + Slog.i(TAG, "deleteWidgetsLocked() provider=" + provider + " userId=" + userId); + } final int N = provider.widgets.size(); for (int i = N - 1; i >= 0; i--) { Widget widget = provider.widgets.get(i); @@ -2955,6 +3003,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = ri.activityInfo; + if (DEBUG) { + Objects.requireNonNull(ri.activityInfo); + } return info; } return null; @@ -2989,6 +3040,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = activityInfo; + if (DEBUG) { + Objects.requireNonNull(activityInfo); + } final Resources resources; final long identity = Binder.clearCallingIdentity(); @@ -3289,6 +3343,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku * Adds the widget to mWidgets and tracks the package name in mWidgetPackages. */ void addWidgetLocked(Widget widget) { + if (DEBUG) { + Slog.i(TAG, "addWidgetLocked() " + widget); + } mWidgets.add(widget); onWidgetProviderAddedOrChangedLocked(widget); @@ -3325,6 +3382,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku * removes the associated package from the cache. */ void removeWidgetLocked(Widget widget) { + if (DEBUG) { + Slog.i(TAG, "removeWidgetLocked() " + widget); + } mWidgets.remove(widget); onWidgetRemovedLocked(widget); scheduleNotifyAppWidgetRemovedLocked(widget); @@ -3359,6 +3419,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku * Clears all widgets and associated cache of packages with bound widgets. */ void clearWidgetsLocked() { + if (DEBUG) { + Slog.i(TAG, "clearWidgetsLocked()"); + } mWidgets.clear(); onWidgetsClearedLocked(); @@ -3564,6 +3627,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku AppWidgetProviderInfo info = new AppWidgetProviderInfo(); info.provider = providerId.componentName; info.providerInfo = providerInfo; + if (DEBUG) { + Objects.requireNonNull(providerInfo); + } provider = new Provider(); provider.setPartialInfoLocked(info); @@ -3580,6 +3646,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku if (info != null) { info.provider = providerId.componentName; info.providerInfo = providerInfo; + if (DEBUG) { + Objects.requireNonNull(providerInfo); + } provider.setInfoLocked(info); } } @@ -3714,6 +3783,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } void onUserStopped(int userId) { + if (DEBUG) { + Slog.i(TAG, "onUserStopped() " + userId); + } synchronized (mLock) { boolean crossProfileWidgetsChanged = false; @@ -3951,6 +4023,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private boolean removeHostsAndProvidersForPackageLocked(String pkgName, int userId) { + if (DEBUG) { + Slog.i(TAG, "removeHostsAndProvidersForPackageLocked() pkg=" + pkgName + + " userId=" + userId); + } boolean removed = removeProvidersForPackageLocked(pkgName, userId); // Delete the hosts for this package too @@ -4509,6 +4585,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // have the bind widget permission have access to the widget. return true; } + if (DEBUG) { + Slog.i(TAG, "canAccessAppWidget() failed. packageName=" + packageName + + " uid=" + uid + " userId=" + userId + " widget=" + widget); + } return false; } @@ -4661,6 +4741,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } if (newInfo != null) { info = newInfo; + if (DEBUG) { + Objects.requireNonNull(info); + } updateGeneratedPreviewCategoriesLocked(); } } @@ -4682,12 +4765,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku @GuardedBy("AppWidgetServiceImpl.mLock") public void setPartialInfoLocked(AppWidgetProviderInfo info) { this.info = info; + if (DEBUG) { + Objects.requireNonNull(this.info); + } mInfoParsed = false; } @GuardedBy("AppWidgetServiceImpl.mLock") public void setInfoLocked(AppWidgetProviderInfo info) { this.info = info; + if (DEBUG) { + Objects.requireNonNull(this.info); + } mInfoParsed = true; } @@ -5714,7 +5803,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/AutofillInlineSuggestionsRequestSession.java b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java index 468b9ab5710e..219b788448e8 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java +++ b/services/autofill/java/com/android/server/autofill/AutofillInlineSuggestionsRequestSession.java @@ -36,6 +36,7 @@ import android.view.inputmethod.InlineSuggestionsResponse; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.server.autofill.ui.InlineFillUi; import com.android.server.inputmethod.InputMethodManagerInternal; @@ -376,8 +377,8 @@ final class AutofillInlineSuggestionsRequestSession { /** * Internal implementation of {@link IInlineSuggestionsRequestCallback}. */ - private static final class InlineSuggestionsRequestCallbackImpl extends - IInlineSuggestionsRequestCallback.Stub { + private static final class InlineSuggestionsRequestCallbackImpl + implements InlineSuggestionsRequestCallback { private final WeakReference<AutofillInlineSuggestionsRequestSession> mSession; @@ -388,7 +389,7 @@ final class AutofillInlineSuggestionsRequestSession { @BinderThread @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { + public void onInlineSuggestionsUnsupported() { if (sDebug) Slog.d(TAG, "onInlineSuggestionsUnsupported() called."); final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { @@ -412,7 +413,7 @@ final class AutofillInlineSuggestionsRequestSession { } @Override - public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { + public void onInputMethodStartInput(AutofillId imeFieldId) { if (sVerbose) Slog.v(TAG, "onInputMethodStartInput() received on " + imeFieldId); final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { @@ -423,7 +424,7 @@ final class AutofillInlineSuggestionsRequestSession { } @Override - public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { + public void onInputMethodShowInputRequested(boolean requestResult) { if (sVerbose) { Slog.v(TAG, "onInputMethodShowInputRequested() received: " + requestResult); } @@ -454,7 +455,7 @@ final class AutofillInlineSuggestionsRequestSession { } @Override - public void onInputMethodFinishInput() throws RemoteException { + public void onInputMethodFinishInput() { if (sVerbose) Slog.v(TAG, "onInputMethodFinishInput() received"); final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { @@ -466,7 +467,7 @@ final class AutofillInlineSuggestionsRequestSession { @BinderThread @Override - public void onInlineSuggestionsSessionInvalidated() throws RemoteException { + public void onInlineSuggestionsSessionInvalidated() { if (sDebug) Slog.d(TAG, "onInlineSuggestionsSessionInvalidated() called."); final AutofillInlineSuggestionsRequestSession session = mSession.get(); if (session != null) { diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java index 970129231f03..6fc05b72da9b 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java @@ -1625,13 +1625,13 @@ public final class AutofillManagerService final class AutoFillManagerServiceStub extends IAutoFillManager.Stub { @Override public void addClient(IAutoFillManagerClient client, ComponentName componentName, - int userId, IResultReceiver receiver) { + int userId, IResultReceiver receiver, boolean credmanRequested) { int flags = 0; try { synchronized (mLock) { final int enabledFlags = getServiceForUserWithLocalBinderIdentityLocked(userId) - .addClientLocked(client, componentName); + .addClientLocked(client, componentName, credmanRequested); if (enabledFlags != 0) { flags |= enabledFlags; } @@ -1644,7 +1644,7 @@ public final class AutofillManagerService } } catch (Exception ex) { // Don't do anything, send back default flags - Log.wtf(TAG, "addClient(): failed " + ex.toString()); + Log.wtf(TAG, "addClient(): failed " + ex.toString(), ex); } finally { send(receiver, flags); } @@ -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 68222298b94c..588266fba47a 100644 --- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java +++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java @@ -33,6 +33,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -96,6 +97,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Random; /** * Bridge between the {@code system_server}'s {@link AutofillManagerService} and the @@ -293,19 +295,31 @@ final class AutofillManagerServiceImpl * @return {@code 0} if disabled, {@code FLAG_ADD_CLIENT_ENABLED} if enabled (it might be * OR'ed with {@code FLAG_AUGMENTED_AUTOFILL_REQUEST}). */ - @GuardedBy("mLock") - int addClientLocked(IAutoFillManagerClient client, ComponentName componentName) { - if (mClients == null) { - mClients = new RemoteCallbackList<>(); - } - mClients.register(client); + int addClientLocked(IAutoFillManagerClient client, ComponentName componentName, + boolean credmanRequested) { + synchronized (mLock) { + ComponentName credComponentName = getCredentialAutofillService(getContext()); + + if (!credmanRequested + && Objects.equals(credComponentName, + mInfo == null ? null : mInfo.getServiceInfo().getComponentName())) { + // If the service component name corresponds to cred component name, then it means + // no autofill provider is selected by the user. Cred Autofill Service should only + // be active if there is a credman request. + return 0; + } + if (mClients == null) { + mClients = new RemoteCallbackList<>(); + } + mClients.register(client); - if (isEnabledLocked()) return FLAG_ADD_CLIENT_ENABLED; + if (isEnabledLocked()) return FLAG_ADD_CLIENT_ENABLED; - // Check if it's enabled for augmented autofill - if (componentName != null && isAugmentedAutofillServiceAvailableLocked() - && isWhitelistedForAugmentedAutofillLocked(componentName)) { - return FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; + // Check if it's enabled for augmented autofill + if (componentName != null && isAugmentedAutofillServiceAvailableLocked() + && isWhitelistedForAugmentedAutofillLocked(componentName)) { + return FLAG_ADD_CLIENT_ENABLED_FOR_AUGMENTED_AUTOFILL_ONLY; + } } // No flags / disabled @@ -452,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); @@ -463,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; } @@ -1486,6 +1516,22 @@ final class AutofillManagerServiceImpl return true; } + @Nullable + private ComponentName getCredentialAutofillService(Context context) { + ComponentName componentName = null; + String credentialManagerAutofillCompName = context.getResources().getString( + R.string.config_defaultCredentialManagerAutofillService); + if (credentialManagerAutofillCompName != null + && !credentialManagerAutofillCompName.isEmpty()) { + componentName = ComponentName.unflattenFromString( + credentialManagerAutofillCompName); + } + if (componentName == null) { + Slog.w(TAG, "Invalid CredentialAutofillService"); + } + return componentName; + } + @GuardedBy("mLock") private int getAugmentedAutofillServiceUidLocked() { if (mRemoteAugmentedAutofillServiceInfo == null) { 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/SaveEventLogger.java b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java index 28e8e3031a14..b7f12adc90d4 100644 --- a/services/autofill/java/com/android/server/autofill/SaveEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/SaveEventLogger.java @@ -34,6 +34,7 @@ import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SAVE_EVENT_RE import static com.android.server.autofill.Helper.sVerbose; import android.annotation.IntDef; +import android.os.SystemClock; import android.util.Slog; import com.android.internal.util.FrameworkStatsLog; @@ -45,7 +46,7 @@ import java.util.Optional; /** * Helper class to log Autofill Save event stats. */ -public final class SaveEventLogger { +public class SaveEventLogger { private static final String TAG = "SaveEventLogger"; /** @@ -112,19 +113,21 @@ public final class SaveEventLogger { public static final int NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG = AUTOFILL_SAVE_EVENT_REPORTED__SAVE_UI_NOT_SHOWN_REASON__NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG; + public static final long UNINITIATED_TIMESTAMP = Long.MIN_VALUE; + private final int mSessionId; private Optional<SaveEventInternal> mEventInternal; + private final long mSessionStartTimestamp; - private SaveEventLogger(int sessionId) { - mSessionId = sessionId; - mEventInternal = Optional.of(new SaveEventInternal()); + private SaveEventLogger(int sessionId, long sessionStartTimestamp) { + mSessionId = sessionId; + mEventInternal = Optional.of(new SaveEventInternal()); + mSessionStartTimestamp = sessionStartTimestamp; } - /** - * A factory constructor to create FillRequestEventLogger. - */ - public static SaveEventLogger forSessionId(int sessionId) { - return new SaveEventLogger(sessionId); + /** A factory constructor to create FillRequestEventLogger. */ + public static SaveEventLogger forSessionId(int sessionId, long sessionStartTimestamp) { + return new SaveEventLogger(sessionId, sessionStartTimestamp); } /** @@ -225,6 +228,13 @@ public final class SaveEventLogger { } /** + * Returns timestamp (relative to mSessionStartTimestamp) + */ + private long getElapsedTime() { + return SystemClock.elapsedRealtime() - mSessionStartTimestamp; + } + + /** * Set latency_save_ui_display_millis as long as mEventInternal presents. */ public void maybeSetLatencySaveUiDisplayMillis(long timestamp) { @@ -233,6 +243,11 @@ public final class SaveEventLogger { }); } + /** Set latency_save_ui_display_millis as long as mEventInternal presents. */ + public void maybeSetLatencySaveUiDisplayMillis() { + maybeSetLatencySaveUiDisplayMillis(getElapsedTime()); + } + /** * Set latency_save_request_millis as long as mEventInternal presents. */ @@ -242,6 +257,11 @@ public final class SaveEventLogger { }); } + /** Set latency_save_request_millis as long as mEventInternal presents. */ + public void maybeSetLatencySaveRequestMillis() { + maybeSetLatencySaveRequestMillis(getElapsedTime()); + } + /** * Set latency_save_finish_millis as long as mEventInternal presents. */ @@ -251,6 +271,11 @@ public final class SaveEventLogger { }); } + /** Set latency_save_finish_millis as long as mEventInternal presents. */ + public void maybeSetLatencySaveFinishMillis() { + maybeSetLatencySaveFinishMillis(getElapsedTime()); + } + /** * Set is_framework_created_save_info as long as mEventInternal presents. */ @@ -261,6 +286,16 @@ public final class SaveEventLogger { } /** + * Set autofill_service_uid as long as mEventInternal presents. + */ + public void maybeSetAutofillServiceUid(int uid) { + mEventInternal.ifPresent( + event -> { + event.mServiceUid = uid; + }); + } + + /** * Log an AUTOFILL_SAVE_EVENT_REPORTED event. */ public void logAndEndEvent() { @@ -287,7 +322,8 @@ public final class SaveEventLogger { + " mLatencySaveUiDisplayMillis=" + event.mLatencySaveUiDisplayMillis + " mLatencySaveRequestMillis=" + event.mLatencySaveRequestMillis + " mLatencySaveFinishMillis=" + event.mLatencySaveFinishMillis - + " mIsFrameworkCreatedSaveInfo=" + event.mIsFrameworkCreatedSaveInfo); + + " mIsFrameworkCreatedSaveInfo=" + event.mIsFrameworkCreatedSaveInfo + + " mServiceUid=" + event.mServiceUid); } FrameworkStatsLog.write( AUTOFILL_SAVE_EVENT_REPORTED, @@ -306,7 +342,8 @@ public final class SaveEventLogger { event.mLatencySaveUiDisplayMillis, event.mLatencySaveRequestMillis, event.mLatencySaveFinishMillis, - event.mIsFrameworkCreatedSaveInfo); + event.mIsFrameworkCreatedSaveInfo, + event.mServiceUid); mEventInternal = Optional.empty(); } @@ -322,11 +359,11 @@ public final class SaveEventLogger { boolean mCancelButtonClicked = false; boolean mDialogDismissed = false; boolean mIsSaved = false; - long mLatencySaveUiDisplayMillis = 0; - long mLatencySaveRequestMillis = 0; - long mLatencySaveFinishMillis = 0; + long mLatencySaveUiDisplayMillis = UNINITIATED_TIMESTAMP; + long mLatencySaveRequestMillis = UNINITIATED_TIMESTAMP; + long mLatencySaveFinishMillis = UNINITIATED_TIMESTAMP; boolean mIsFrameworkCreatedSaveInfo = false; - + int mServiceUid = -1; SaveEventInternal() { } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index cd1ef882868a..a8b123518db3 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1168,6 +1168,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } + @GuardedBy("mLock") @Nullable private AutofillValue findValueFromThisSessionOnlyLocked(@NonNull AutofillId autofillId) { final ViewState state = mViewStates.get(autofillId); @@ -1176,9 +1177,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } AutofillValue value = state.getCurrentValue(); + + // Some app clears the form before navigating to another activities. In this case, use the + // cached value instead. + if (value == null || value.isEmpty()) { + AutofillValue candidateSaveValue = state.getCandidateSaveValue(); + if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { + if (sDebug) { + Slog.d(TAG, "findValueLocked(): current value for " + autofillId + + " is empty, using candidateSaveValue instead."); + } + return candidateSaveValue; + } + } if (value == null) { - if (sDebug) Slog.d(TAG, "findValueLocked(): no current value for " + autofillId); - value = getValueFromContextsLocked(autofillId); + if (sDebug) { + Slog.d(TAG, "findValueLocked(): no current value for " + autofillId + + ", checking value from previous fill contexts"); + value = getValueFromContextsLocked(autofillId); + } } return value; } @@ -1336,8 +1353,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested); mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( mFieldClassificationIdSnapshot); + mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); mFillRequestEventLogger.maybeSetRequestId(requestId); mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); + mSaveEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); + mSessionCommittedEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid()); if (mSessionFlags.mInlineSupportedByService) { mFillRequestEventLogger.maybeSetInlineSuggestionHostUid(mContext, userId); } @@ -1493,10 +1513,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mCredentialAutofillService = getCredentialAutofillService(context); - ComponentName primaryServiceComponentName, secondaryServiceComponentName; + ComponentName primaryServiceComponentName, secondaryServiceComponentName = null; if (isPrimaryCredential) { primaryServiceComponentName = mCredentialAutofillService; - secondaryServiceComponentName = serviceComponentName; + if (serviceComponentName != null + && !serviceComponentName.equals(mCredentialAutofillService)) { + // if service component name is credential autofill service, no need to initialize + // secondary provider. This happens if the user sets non-autofill provider as + // password provider. + secondaryServiceComponentName = serviceComponentName; + } } else { primaryServiceComponentName = serviceComponentName; secondaryServiceComponentName = mCredentialAutofillService; @@ -1531,7 +1557,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFillResponseEventLogger = FillResponseEventLogger.forSessionId(sessionId); mSessionCommittedEventLogger = SessionCommittedEventLogger.forSessionId(sessionId); mSessionCommittedEventLogger.maybeSetComponentPackageUid(uid); - mSaveEventLogger = SaveEventLogger.forSessionId(sessionId); + mSaveEventLogger = SaveEventLogger.forSessionId(sessionId, mLatencyBaseTime); mIsPrimaryCredential = isPrimaryCredential; mIgnoreViewStateResetToEmpty = AutofillFeatureFlags.shouldIgnoreViewStateResetToEmpty(); @@ -2440,9 +2466,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags.mShowingSaveUi = false; // Log onSaveRequest result. mSaveEventLogger.maybeSetIsSaved(true); - final long saveRequestFinishTimestamp = - SystemClock.elapsedRealtime() - mLatencyBaseTime; - mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); + mSaveEventLogger.maybeSetLatencySaveFinishMillis(); mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " @@ -2473,9 +2497,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; // Log onSaveRequest result. - final long saveRequestFinishTimestamp = - SystemClock.elapsedRealtime() - mLatencyBaseTime; - mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp); + mSaveEventLogger.maybeSetLatencySaveFinishMillis(); mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " @@ -2621,8 +2643,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } } - final long saveRequestStartTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime; - mSaveEventLogger.maybeSetLatencySaveRequestMillis(saveRequestStartTimestamp); + mSaveEventLogger.maybeSetLatencySaveRequestMillis(); mHandler.sendMessage(obtainMessage( AutofillManagerServiceImpl::handleSessionSave, mService, this)); @@ -3713,19 +3734,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState AutofillValue value = viewState.getCurrentValue(); if (value == null || value.isEmpty()) { - final AutofillValue initialValue = getValueFromContextsLocked(id); - if (initialValue != null) { - if (sDebug) { - Slog.d(TAG, "Value of required field " + id + " didn't change; " - + "using initial value (" + initialValue + ") instead"); + // Some apps clear the form before navigating to other activities. + // If current value is empty, consider fall back to last cached + // non-empty result first. + final AutofillValue candidateSaveValue = + viewState.getCandidateSaveValue(); + if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { + if (sVerbose) { + Slog.v(TAG, "current value is empty, using cached last non-empty " + + "value instead"); } - value = initialValue; + value = candidateSaveValue; } else { - if (sDebug) { - Slog.d(TAG, "empty value for required " + id ); + // If candidate save value is also empty, consider falling back to initial + // value in context. + final AutofillValue initialValue = getValueFromContextsLocked(id); + if (initialValue != null) { + if (sDebug) { + Slog.d(TAG, "Value of required field " + id + " didn't change; " + + "using initial value (" + initialValue + ") instead"); + } + value = initialValue; + } else { + if (sDebug) { + Slog.d(TAG, "empty value for required " + id); + } + allRequiredAreNotEmpty = false; + break; } - allRequiredAreNotEmpty = false; - break; } } @@ -3797,7 +3833,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState continue; } if ((viewState.getState() & ViewState.STATE_CHANGED) != 0) { - final AutofillValue currentValue = viewState.getCurrentValue(); + AutofillValue currentValue = viewState.getCurrentValue(); + if (currentValue == null || currentValue.isEmpty()) { + // Some apps clear the form before navigating to other activities. + // If current value is empty, consider fall back to last cached + // non-empty result instead. + final AutofillValue candidateSaveValue = + viewState.getCandidateSaveValue(); + if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { + if (sVerbose) { + Slog.v(TAG, "current value is empty, using cached last " + + "non-empty value instead"); + } + currentValue = candidateSaveValue; + } + } final AutofillValue value = getSanitizedValue(sanitizers, id, currentValue); if (value == null) { if (sDebug) { @@ -3931,13 +3981,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, Event.NO_SAVE_UI_REASON_NONE); } - final long saveUiDisplayStartTimestamp = SystemClock.elapsedRealtime(); getUiForShowing().showSaveUi(serviceLabel, serviceIcon, mService.getServicePackageName(), saveInfo, this, mComponentName, this, mContext, mPendingSaveUi, isUpdate, mCompatMode, response.getShowSaveDialogIcon(), mSaveEventLogger); - mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis( - SystemClock.elapsedRealtime()- saveUiDisplayStartTimestamp); if (client != null) { try { client.setSaveUiState(id, true); @@ -4668,6 +4715,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFieldClassificationIdSnapshot); mPresentationStatsEventLogger.maybeSetAvailableCount( response.getDatasets(), mCurrentViewId); + mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId); } @GuardedBy("mLock") @@ -4712,14 +4760,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value, ViewState viewState, int flags) { + // Cache the last non-empty value for save purpose. Some apps clear the form before + // navigating to other activities. if (mIgnoreViewStateResetToEmpty && (value == null || value.isEmpty()) && viewState.getCurrentValue() != null && viewState.getCurrentValue().isText() && viewState.getCurrentValue().getTextValue() != null && viewState.getCurrentValue().getTextValue().length() > 1) { if (sVerbose) { - Slog.v(TAG, "Ignoring view state reset to empty on id " + id); + Slog.v(TAG, "value is resetting to empty, caching the last non-empty value"); } - return; + viewState.setCandidateSaveValue(viewState.getCurrentValue()); + } else { + viewState.setCandidateSaveValue(null); } final String textValue; if (value == null || !value.isText()) { @@ -5380,7 +5432,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") @@ -6588,7 +6653,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/SessionCommittedEventLogger.java b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java index cd37073a1404..1be8548c788f 100644 --- a/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/SessionCommittedEventLogger.java @@ -17,21 +17,15 @@ package com.android.server.autofill; import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; + import static com.android.internal.util.FrameworkStatsLog.AUTOFILL_SESSION_COMMITTED; import static com.android.server.autofill.Helper.sVerbose; -import android.annotation.IntDef; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.provider.Settings; -import android.text.TextUtils; import android.util.Slog; import android.view.autofill.AutofillManager.AutofillCommitReason; + import com.android.internal.util.FrameworkStatsLog; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.Optional; /** @@ -91,6 +85,14 @@ public final class SessionCommittedEventLogger { }); } + /** Set autofill_service_uid as long as mEventInternal presents. */ + public void maybeSetAutofillServiceUid(int uid) { + mEventInternal.ifPresent( + event -> { + event.mServiceUid = uid; + }); + } + /** * Log an AUTOFILL_SESSION_COMMITTED event. */ @@ -106,7 +108,8 @@ public final class SessionCommittedEventLogger { + " mComponentPackageUid=" + event.mComponentPackageUid + " mRequestCount=" + event.mRequestCount + " mCommitReason=" + event.mCommitReason - + " mSessionDurationMillis=" + event.mSessionDurationMillis); + + " mSessionDurationMillis=" + event.mSessionDurationMillis + + " mServiceUid=" + event.mServiceUid); } FrameworkStatsLog.write( AUTOFILL_SESSION_COMMITTED, @@ -114,7 +117,8 @@ public final class SessionCommittedEventLogger { event.mComponentPackageUid, event.mRequestCount, event.mCommitReason, - event.mSessionDurationMillis); + event.mSessionDurationMillis, + event.mServiceUid); mEventInternal = Optional.empty(); } @@ -123,6 +127,7 @@ public final class SessionCommittedEventLogger { int mRequestCount = 0; int mCommitReason = COMMIT_REASON_UNKNOWN; long mSessionDurationMillis = 0; + int mServiceUid = -1; SessionCommittedEventInternal() { } diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java index fec5aa531cdd..6ad0eb6ff54f 100644 --- a/services/autofill/java/com/android/server/autofill/ViewState.java +++ b/services/autofill/java/com/android/server/autofill/ViewState.java @@ -106,6 +106,15 @@ final class ViewState { */ private FillResponse mSecondaryFillResponse; private AutofillValue mCurrentValue; + + /** + * Some apps clear the form before navigating to another activity. The mCandidateSaveValue + * caches the value when a field with string longer than 2 characters are cleared. + * + * When showing save UI, if mCurrentValue of view state is empty, session would use + * mCandidateSaveValue to prompt save instead. + */ + private AutofillValue mCandidateSaveValue; private AutofillValue mAutofilledValue; private AutofillValue mSanitizedValue; private Rect mVirtualBounds; @@ -139,6 +148,18 @@ final class ViewState { mCurrentValue = value; } + /** + * Gets the candidate save value of the view. + */ + @Nullable + AutofillValue getCandidateSaveValue() { + return mCandidateSaveValue; + } + + void setCandidateSaveValue(AutofillValue value) { + mCandidateSaveValue = value; + } + @Nullable AutofillValue getAutofilledValue() { return mAutofilledValue; @@ -268,6 +289,9 @@ final class ViewState { if (mCurrentValue != null) { builder.append(", currentValue:" ).append(mCurrentValue); } + if (mCandidateSaveValue != null) { + builder.append(", candidateSaveValue:").append(mCandidateSaveValue); + } if (mAutofilledValue != null) { builder.append(", autofilledValue:" ).append(mAutofilledValue); } @@ -302,6 +326,9 @@ final class ViewState { if (mAutofilledValue != null) { pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue); } + if (mCandidateSaveValue != null) { + pw.print(prefix); pw.print("candidateSaveValue:"); pw.println(mCandidateSaveValue); + } if (mSanitizedValue != null) { pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue); } diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 602855da7ddf..3b9c54f79e61 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -413,6 +413,8 @@ public final class AutoFillUI { callback.startIntentSender(intentSender, intent); } }, mUiModeMgr.isNightMode(), isUpdate, compatMode, showServiceIcon); + + mSaveEventLogger.maybeSetLatencySaveUiDisplayMillis(); }); } 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/securechannel/SecureChannel.java b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java index 0e66fbc020a1..71a182225013 100644 --- a/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java +++ b/services/companion/java/com/android/server/companion/securechannel/SecureChannel.java @@ -23,6 +23,7 @@ import android.content.Context; import android.os.Build; import android.util.Slog; +import com.google.security.cryptauth.lib.securegcm.ukey2.AlertException; import com.google.security.cryptauth.lib.securegcm.ukey2.BadHandleException; import com.google.security.cryptauth.lib.securegcm.ukey2.CryptoException; import com.google.security.cryptauth.lib.securegcm.ukey2.D2DConnectionContextV1; @@ -203,7 +204,8 @@ public class SecureChannel { * * This method must only be called from one of the two participants. */ - public void establishSecureConnection() throws IOException, SecureChannelException { + public void establishSecureConnection() throws IOException, + SecureChannelException, HandshakeException { if (isSecured()) { Slog.d(TAG, "Channel is already secure."); return; @@ -334,7 +336,7 @@ public class SecureChannel { } } - private void initiateHandshake() throws IOException, BadHandleException { + private void initiateHandshake() throws IOException, BadHandleException , HandshakeException { if (mConnectionContext != null) { Slog.d(TAG, "Ukey2 handshake is already completed."); return; @@ -394,8 +396,8 @@ public class SecureChannel { } } - private void exchangeHandshake() - throws IOException, HandshakeException, BadHandleException, CryptoException { + private void exchangeHandshake() throws IOException, HandshakeException, + BadHandleException, CryptoException, AlertException { if (mConnectionContext != null) { Slog.d(TAG, "Ukey2 handshake is already completed."); return; 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 9b72288955a7..70af49c88433 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -41,7 +41,6 @@ import android.os.RemoteException; import android.util.ArrayMap; import android.util.Log; import android.util.Slog; -import android.view.Display; import android.view.InputDevice; import android.view.WindowManager; @@ -169,7 +168,6 @@ class InputController { createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId, deviceToken, displayId, phys, () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys)); - setVirtualMousePointerDisplayId(displayId); } void createTouchscreen(@NonNull String deviceName, int vendorId, int productId, @@ -226,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) { @@ -236,15 +234,6 @@ class InputController { if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_KEYBOARD) { mInputManagerInternal.removeKeyboardLayoutAssociation(phys); } - - // Reset values to the default if all virtual mice are unregistered, or set display - // id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be - // removed from the mInputDeviceDescriptors instance variable prior to this point. - if (inputDeviceDescriptor.isMouse()) { - if (getVirtualMousePointerDisplayId() == inputDeviceDescriptor.getDisplayId()) { - updateActivePointerDisplayIdLocked(); - } - } } /** @@ -276,29 +265,6 @@ class InputController { mWindowManager.setDisplayImePolicy(displayId, policy); } - // TODO(b/293587049): Remove after pointer icon refactor is complete. - @GuardedBy("mLock") - private void updateActivePointerDisplayIdLocked() { - InputDeviceDescriptor mostRecentlyCreatedMouse = null; - for (int i = 0; i < mInputDeviceDescriptors.size(); ++i) { - InputDeviceDescriptor otherInputDeviceDescriptor = mInputDeviceDescriptors.valueAt(i); - if (otherInputDeviceDescriptor.isMouse()) { - if (mostRecentlyCreatedMouse == null - || (otherInputDeviceDescriptor.getCreationOrderNumber() - > mostRecentlyCreatedMouse.getCreationOrderNumber())) { - mostRecentlyCreatedMouse = otherInputDeviceDescriptor; - } - } - } - if (mostRecentlyCreatedMouse != null) { - setVirtualMousePointerDisplayId( - mostRecentlyCreatedMouse.getDisplayId()); - } else { - // All mice have been unregistered - setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY); - } - } - /** * Validates a device name by checking whether a device with the same name already exists. * @param deviceName The name of the device to be validated @@ -321,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) { @@ -355,9 +321,6 @@ class InputController { if (inputDeviceDescriptor == null) { return false; } - if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) { - setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId()); - } return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(), event.getButtonCode(), event.getAction(), event.getEventTimeNanos()); } @@ -384,9 +347,6 @@ class InputController { if (inputDeviceDescriptor == null) { return false; } - if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) { - setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId()); - } return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(), event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos()); } @@ -399,9 +359,6 @@ class InputController { if (inputDeviceDescriptor == null) { return false; } - if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) { - setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId()); - } return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(), event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos()); } @@ -415,9 +372,6 @@ class InputController { throw new IllegalArgumentException( "Could not get cursor position for input device for given token"); } - if (inputDeviceDescriptor.getDisplayId() != getVirtualMousePointerDisplayId()) { - setVirtualMousePointerDisplayId(inputDeviceDescriptor.getDisplayId()); - } return LocalServices.getService(InputManagerInternal.class).getCursorPosition( inputDeviceDescriptor.getDisplayId()); } @@ -835,7 +789,7 @@ class InputController { throw e; } } catch (DeviceCreationException e) { - InputManagerGlobal.getInstance().removeUniqueIdAssociation(phys); + InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); throw e; } @@ -878,22 +832,4 @@ class InputController { /** Returns true if the calling thread is a valid thread for device creation. */ boolean isValidThread(); } - - // TODO(b/293587049): Remove after pointer icon refactor is complete. - private void setVirtualMousePointerDisplayId(int displayId) { - if (com.android.input.flags.Flags.enablePointerChoreographer()) { - // We no longer need to set the pointer display when pointer choreographer is enabled. - return; - } - mInputManagerInternal.setVirtualMousePointerDisplayId(displayId); - } - - // TODO(b/293587049): Remove after pointer icon refactor is complete. - private int getVirtualMousePointerDisplayId() { - if (com.android.input.flags.Flags.enablePointerChoreographer()) { - // We no longer need to get the pointer display when pointer choreographer is enabled. - return Display.INVALID_DISPLAY; - } - return mInputManagerInternal.getVirtualMousePointerDisplayId(); - } } diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING index 340bc327fc7f..caa877c2b964 100644 --- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING +++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING @@ -81,9 +81,6 @@ "name": "CtsPermissionTestCases", "options": [ { - "include-filter": "android.permissionmultidevice.cts.DeviceAwarePermissionGrantTest" - }, - { "include-filter": "android.permission.cts.DevicePermissionsTest" }, { @@ -93,6 +90,14 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsPermissionMultiDeviceTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] } ] } diff --git a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java index 4547bd6c64ef..743086ee1c16 100644 --- a/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java +++ b/services/companion/java/com/android/server/companion/virtual/camera/VirtualCameraController.java @@ -187,9 +187,9 @@ public final class VirtualCameraController implements IBinder.DeathRecipient { fout.println(indent + "VirtualCameraController:"); indent += indent; synchronized (mCameras) { - fout.printf("%sRegistered cameras:%d%n\n", indent, mCameras.size()); + fout.println(indent + "Registered cameras: " + mCameras.size()); for (CameraDescriptor descriptor : mCameras.values()) { - fout.printf("%s token: %s\n", indent, descriptor.mConfig); + fout.println(indent + " token: " + descriptor.mConfig); } } } diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index 9a73a2d75419..f5db6e962d01 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -27,9 +27,9 @@ import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; +import static android.view.WindowManager.LayoutParams.TYPE_POINTER; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; -import static com.android.server.contextualsearch.flags.Flags.enableExcludePersistentUi; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT; import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE; @@ -286,13 +286,11 @@ public class ContextualSearchManagerService extends SystemService { } final ScreenCapture.ScreenshotHardwareBuffer shb; if (mWmInternal != null) { - if (enableExcludePersistentUi()) { - shb = mWmInternal.takeAssistScreenshot( - Set.of(TYPE_STATUS_BAR, TYPE_NAVIGATION_BAR, TYPE_NAVIGATION_BAR_PANEL)); - } else { - shb = mWmInternal.takeAssistScreenshot(/* windowTypesToExclude= */ Set.of()); - } - + shb = mWmInternal.takeAssistScreenshot(Set.of( + TYPE_STATUS_BAR, + TYPE_NAVIGATION_BAR, + TYPE_NAVIGATION_BAR_PANEL, + TYPE_POINTER)); } else { shb = null; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 3ff05041d32d..0fdf6d0fd507 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -232,6 +232,7 @@ java_library_static { "android.hardware.rebootescrow-V1-java", "android.hardware.power.stats-V2-java", "android.hidl.manager-V1.2-java", + "audio-permission-aidl-java", "cbor-java", "com.android.media.audio-aconfig-java", "icu4j_calendar_astronomer", @@ -258,6 +259,7 @@ java_library_static { "core_os_flags_lib", "connectivity_flags_lib", "dreams_flags_lib", + "aconfig_new_storage_flags_lib", ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java index 9f279b1ba3fe..f69a521130ab 100644 --- a/services/core/java/com/android/server/BootReceiver.java +++ b/services/core/java/com/android/server/BootReceiver.java @@ -34,6 +34,7 @@ import android.os.TombstoneWithHeadersProto; import android.provider.Downloads; import android.system.ErrnoException; import android.system.Os; +import android.system.OsConstants; import android.text.TextUtils; import android.util.AtomicFile; import android.util.EventLog; @@ -230,16 +231,23 @@ public class BootReceiver extends BroadcastReceiver { } private static String getCurrentBootHeaders() throws IOException { - return new StringBuilder(512) - .append("Build: ").append(Build.FINGERPRINT).append("\n") - .append("Hardware: ").append(Build.BOARD).append("\n") - .append("Revision: ") - .append(SystemProperties.get("ro.revision", "")).append("\n") - .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") - .append("Radio: ").append(Build.getRadioVersion()).append("\n") - .append("Kernel: ") - .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")) - .append("\n").toString(); + StringBuilder builder = new StringBuilder(512) + .append("Build: ").append(Build.FINGERPRINT).append("\n") + .append("Hardware: ").append(Build.BOARD).append("\n") + .append("Revision: ") + .append(SystemProperties.get("ro.revision", "")).append("\n") + .append("Bootloader: ").append(Build.BOOTLOADER).append("\n") + .append("Radio: ").append(Build.getRadioVersion()).append("\n") + .append("Kernel: ") + .append(FileUtils.readTextFile(new File("/proc/version"), 1024, "...\n")); + + // If device is not using 4KB pages, add the PageSize + long pageSize = Os.sysconf(OsConstants._SC_PAGESIZE); + if (pageSize != 4096) { + builder.append("PageSize: ").append(pageSize).append("\n"); + } + builder.append("\n"); + return builder.toString(); } 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..ef03888d6620 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -21,6 +21,7 @@ import static android.app.ActivityManager.UID_OBSERVER_GONE; import static android.os.Process.SYSTEM_UID; import static com.android.server.flags.Flags.pinWebview; +import static com.android.server.flags.Flags.skipHomeArtPins; import android.annotation.EnforcePermission; import android.annotation.IntDef; @@ -121,7 +122,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 +176,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 +238,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 +390,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 +594,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 +815,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: @@ -852,6 +852,9 @@ public final class PinnerService extends SystemService { } int apkPinSizeLimit = pinSizeLimit; + + boolean shouldSkipArtPins = key == KEY_HOME && skipHomeArtPins(); + for (String apk: apks) { if (apkPinSizeLimit <= 0) { Slog.w(TAG, "Reached to the pin size limit. Skipping: " + apk); @@ -871,11 +874,12 @@ public final class PinnerService extends SystemService { } synchronized (this) { pinnedApp.mFiles.add(pf); + mPinnedFiles.put(pf.fileName, pf); } apkPinSizeLimit -= pf.bytesPinned; - if (apk.equals(appInfo.sourceDir)) { - pinOptimizedDexDependencies(pf, apkPinSizeLimit, appInfo); + if (apk.equals(appInfo.sourceDir) && !shouldSkipArtPins) { + pinOptimizedDexDependencies(pf, Integer.MAX_VALUE, appInfo); } } } @@ -921,8 +925,8 @@ public final class PinnerService extends SystemService { } pf.groupName = groupName != null ? groupName : ""; - maxBytesToPin -= bytesPinned; bytesPinned += pf.bytesPinned; + maxBytesToPin -= bytesPinned; synchronized (this) { mPinnedFiles.put(pf.fileName, pf); @@ -970,7 +974,7 @@ public final class PinnerService extends SystemService { // Unpin if it was already pinned prior to re-pinning. unpinFile(file); - PinnedFile df = mInjector.pinFileInternal(file, Integer.MAX_VALUE, + PinnedFile df = mInjector.pinFileInternal(file, maxBytesToPin, /*attemptPinIntrospection=*/false); if (df == null) { Slog.i(TAG, "Failed to pin ART file = " + file); @@ -1342,18 +1346,6 @@ public final class PinnerService extends SystemService { public List<PinnedFileStat> getPinnerStats() { ArrayList<PinnedFileStat> stats = new ArrayList<>(); - Collection<PinnedApp> pinnedApps; - synchronized(this) { - pinnedApps = mPinnedApps.values(); - } - for (PinnedApp pinnedApp : pinnedApps) { - for (PinnedFile pf : pinnedApp.mFiles) { - PinnedFileStat stat = - new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName); - stats.add(stat); - } - } - Collection<PinnedFile> pinnedFiles; synchronized(this) { pinnedFiles = mPinnedFiles.values(); diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java index 64bca33569cc..04edb5756599 100644 --- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java +++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java @@ -27,6 +27,7 @@ import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDI import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START; import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP; import static com.android.internal.util.FrameworkStatsLog.SENSITIVE_NOTIFICATION_APP_PROTECTION_SESSION; +import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener; import android.annotation.NonNull; import android.annotation.Nullable; @@ -210,6 +211,12 @@ public final class SensitiveContentProtectionManagerService extends SystemServic } }; + private final OnWindowRemovedListener mOnWindowRemovedListener = token -> { + synchronized (mSensitiveContentProtectionLock) { + mPackagesShowingSensitiveContent.removeIf(pkgInfo -> pkgInfo.getWindowToken() == token); + } + }; + public SensitiveContentProtectionManagerService(@NonNull Context context) { super(context); if (sensitiveNotificationAppProtection()) { @@ -265,6 +272,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic // Intra-process call, should never happen. } } + + if (sensitiveContentAppProtection()) { + mWindowManager.registerOnWindowRemovedListener(mOnWindowRemovedListener); + } } /** Cleanup any callbacks and listeners */ diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index a508ebfdb950..8c1bb3b0cb71 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -1783,7 +1783,13 @@ public class SystemConfig { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { int gid = Process.getGidForName(gidStr); - perm.gids = appendInt(perm.gids, gid); + if (gid != -1) { + perm.gids = appendInt(perm.gids, gid); + } else { + Slog.w(TAG, "<group> with unknown gid \"" + + gidStr + " for permission " + name + " in " + + parser.getPositionDescription()); + } } else { Slog.w(TAG, "<group> without gid at " + parser.getPositionDescription()); diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING index 5933639f2317..a3b6d806c6ce 100644 --- a/services/core/java/com/android/server/TEST_MAPPING +++ b/services/core/java/com/android/server/TEST_MAPPING @@ -134,6 +134,13 @@ }, { "name": "CtsSuspendAppsTestCases" + }, + { + "name": "CtsWindowManagerBackgroundActivityTestCases", + "file_patterns": [ + "Background.*\\.java", + "Activity.*\\.java" + ] } ], "presubmit-large": [ @@ -187,6 +194,18 @@ }, { "name": "SelinuxFrameworksTests" + }, + { + "name": "WmTests", + "file_patterns": [ + "Background.*\\.java", + "Activity.*\\.java" + ], + "options": [ + { + "include-filter": "com.android.server.wm.BackgroundActivityStart*" + } + ] } - ] + ] } 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/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 7acca19f9d79..e2b6bd6ae360 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -36,6 +36,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; @@ -47,6 +48,7 @@ import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; +import android.net.vcn.Flags; import android.net.vcn.IVcnManagementService; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; @@ -68,6 +70,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; +import android.os.UserManager; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -431,6 +434,8 @@ public class VcnManagementService extends IVcnManagementService.Stub { mTelephonySubscriptionTracker.register(); } + // The system server automatically has the required permissions for #getMainUser() + @SuppressLint("AndroidFrameworkRequiresPermission") private void enforcePrimaryUser() { final int uid = mDeps.getBinderCallingUid(); if (uid == Process.SYSTEM_UID) { @@ -438,7 +443,20 @@ public class VcnManagementService extends IVcnManagementService.Stub { "Calling identity was System Server. Was Binder calling identity cleared?"); } - if (!UserHandle.getUserHandleForUid(uid).isSystem()) { + final UserHandle userHandle = UserHandle.getUserHandleForUid(uid); + + if (Flags.enforceMainUser()) { + final UserManager userManager = mContext.getSystemService(UserManager.class); + + Binder.withCleanCallingIdentity( + () -> { + if (!Objects.equals(userManager.getMainUser(), userHandle)) { + throw new SecurityException( + "VcnManagementService can only be used by callers running as" + + " the main user"); + } + }); + } else if (!userHandle.isSystem()) { throw new SecurityException( "VcnManagementService can only be used by callers running as the primary user"); } diff --git a/services/core/java/com/android/server/WallpaperUpdateReceiver.java b/services/core/java/com/android/server/WallpaperUpdateReceiver.java index 2812233815a6..42391a55fed6 100644 --- a/services/core/java/com/android/server/WallpaperUpdateReceiver.java +++ b/services/core/java/com/android/server/WallpaperUpdateReceiver.java @@ -24,7 +24,6 @@ import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.os.AsyncTask; import android.os.ParcelFileDescriptor; import android.util.Slog; @@ -59,10 +58,10 @@ public class WallpaperUpdateReceiver extends BroadcastReceiver { return; } if (DEBUG) Slog.d(TAG, "Set customized default_wallpaper."); - Bitmap blank = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); - // set a blank wallpaper to force a redraw of default_wallpaper - wallpaperManager.setBitmap(blank); - wallpaperManager.setResource(com.android.internal.R.drawable.default_wallpaper); + // Check if it is not a live wallpaper set + if (wallpaperManager.getWallpaperInfo() == null) { + wallpaperManager.clearWallpaper(); + } } catch (Exception e) { Slog.w(TAG, "Failed to customize system wallpaper." + e); } 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 ec0d8974603e..9be0e1ff464e 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; @@ -507,6 +508,18 @@ public final class ActiveServices { */ final SparseArray<SparseArray<TimeLimitedFgsInfo>> mTimeLimitedFgsInfo = new SparseArray<>(); + /** + * Foreground services of certain types will now have a time limit. If the foreground service + * of the offending type is not stopped within the allocated time limit, it will receive a + * callback via {@link Service#onTimeout(int, int)} and it must then be stopped within a few + * seconds. If an app fails to do so, it will be declared an ANR. + * + * @see Service#onTimeout(int, int) onTimeout callback for additional details + */ + @ChangeId + @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM) + static final long FGS_INTRODUCE_TIME_LIMITS = 317799821L; + // allowlisted packageName. ArraySet<String> mAllowListWhileInUsePermissionInFgs = new ArraySet<>(); @@ -772,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"); } @@ -1156,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) { @@ -2396,11 +2407,16 @@ public final class ActiveServices { // "if (r.mAllowStartForeground == REASON_DENIED...)" block below. } } - } else if (getTimeLimitedFgsType(foregroundServiceType) - != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) { + } else if (CompatChanges.isChangeEnabled( + FGS_INTRODUCE_TIME_LIMITS, r.appInfo.uid) + && android.app.Flags.introduceNewServiceOntimeoutCallback() + && getTimeLimitedFgsType(foregroundServiceType) + != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) { // Calling startForeground on a FGS type which has a time limit will only be // allowed if the app is in a state where it can normally start another FGS - // and it hasn't hit the time limit for that type in the past 24hrs. + // and it hasn't hit its time limit in the past 24hrs, or it has been in the + // foreground after it hit its time limit, or it is currently in the + // TOP (or better) proc state. // See if the app could start an FGS or not. r.clearFgsAllowStart(); @@ -2426,19 +2442,32 @@ public final class ActiveServices { SystemClock.elapsedRealtime() - (24 * 60 * 60 * 1000)); final long lastTimeOutAt = fgsTypeInfo.getTimeLimitExceededAt(); if (fgsTypeInfo.getFirstFgsStartRealtime() < before24Hr + || r.app.mState.getCurProcState() <= PROCESS_STATE_TOP || (lastTimeOutAt != Long.MIN_VALUE && r.app.mState.getLastTopTime() > lastTimeOutAt)) { // Reset the time limit info for this fgs type if it has been - // more than 24hrs since the first fgs start or if the app was - // in the TOP state after time limit was exhausted. + // more than 24hrs since the first fgs start or if the app is + // currently in the TOP state or was in the TOP state after + // the time limit was exhausted previously. fgsTypeInfo.reset(); } 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 { @@ -2686,7 +2715,10 @@ public final class ActiveServices { mAm.notifyPackageUse(r.serviceInfo.packageName, PackageManager.NOTIFY_PACKAGE_USE_FOREGROUND_SERVICE); - maybeUpdateFgsTrackingLocked(r, previousFgsType); + if (CompatChanges.isChangeEnabled(FGS_INTRODUCE_TIME_LIMITS, r.appInfo.uid) + && android.app.Flags.introduceNewServiceOntimeoutCallback()) { + maybeUpdateFgsTrackingLocked(r, previousFgsType); + } } else { if (DEBUG_FOREGROUND_SERVICE) { Slog.d(TAG, "Suppressing startForeground() for FAS " + r); @@ -3751,6 +3783,14 @@ public final class ActiveServices { return fgsInfo.getLastFgsStartTime() + Math.max(0, timeLimit - fgsInfo.getTotalRuntime()); } + private TimeLimitedFgsInfo getFgsTimeLimitedInfo(int uid, int fgsType) { + final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(uid); + if (fgsInfo != null) { + return fgsInfo.get(fgsType); + } + return null; + } + private void maybeUpdateFgsTrackingLocked(ServiceRecord sr, int previousFgsType) { final int previouslyTimeLimitedType = getTimeLimitedFgsType(previousFgsType); if (previouslyTimeLimitedType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE @@ -3761,16 +3801,12 @@ public final class ActiveServices { if (previouslyTimeLimitedType != ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE) { // FGS is switching types and the previous type was time-limited so update the runtime. - final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid); - if (fgsInfo != null) { - final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(previouslyTimeLimitedType); - if (fgsTypeInfo != null) { - // Update the total runtime for the previous time-limited fgs type. - fgsTypeInfo.updateTotalRuntime(); - // TODO(b/330399444): handle the case where an app is running 2 services of the - // same time-limited type in parallel and stops one of them which leads to the - // second running one gaining additional runtime. - } + final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo( + sr.appInfo.uid, previouslyTimeLimitedType); + if (fgsTypeInfo != null) { + // Update the total runtime for the previous time-limited fgs type. + fgsTypeInfo.updateTotalRuntime(SystemClock.uptimeMillis()); + fgsTypeInfo.decNumParallelServices(); } if (!sr.isFgsTimeLimited()) { @@ -3793,10 +3829,10 @@ public final class ActiveServices { final int timeLimitedFgsType = getTimeLimitedFgsType(sr.foregroundServiceType); TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedFgsType); if (fgsTypeInfo == null) { - fgsTypeInfo = sr.createTimeLimitedFgsInfo(nowUptime); + fgsTypeInfo = sr.createTimeLimitedFgsInfo(); fgsInfo.put(timeLimitedFgsType, fgsTypeInfo); } - fgsTypeInfo.setLastFgsStartTime(nowUptime); + fgsTypeInfo.noteFgsFgsStart(nowUptime); // We'll cancel the previous ANR timer and start a fresh one below. mFGSAnrTimer.cancel(sr); @@ -3820,13 +3856,12 @@ public final class ActiveServices { return; // if the current fgs type is not time-limited, return. } - final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid); - if (fgsInfo != null) { - final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(timeLimitedType); - if (fgsTypeInfo != null) { - // Update the total runtime for the previous time-limited fgs type. - fgsTypeInfo.updateTotalRuntime(); - } + final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo( + sr.appInfo.uid, timeLimitedType); + if (fgsTypeInfo != null) { + // Update the total runtime for the previous time-limited fgs type. + fgsTypeInfo.updateTotalRuntime(SystemClock.uptimeMillis()); + fgsTypeInfo.decNumParallelServices(); } Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr); mFGSAnrTimer.cancel(sr); @@ -3881,24 +3916,21 @@ public final class ActiveServices { mFGSAnrTimer.accept(sr); traceInstant("FGS timed out: ", sr); - final SparseArray<TimeLimitedFgsInfo> fgsInfo = mTimeLimitedFgsInfo.get(sr.appInfo.uid); - if (fgsInfo != null) { - final TimeLimitedFgsInfo fgsTypeInfo = fgsInfo.get(fgsType); - if (fgsTypeInfo != null) { - // Update total runtime for the time-limited fgs type and mark it as timed out. - fgsTypeInfo.updateTotalRuntime(); - fgsTypeInfo.setTimeLimitExceededAt(nowUptime); - - logFGSStateChangeLocked(sr, - FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT, - nowUptime > fgsTypeInfo.getLastFgsStartTime() - ? (int) (nowUptime - fgsTypeInfo.getLastFgsStartTime()) : 0, - FGS_STOP_REASON_UNKNOWN, - FGS_TYPE_POLICY_CHECK_UNKNOWN, - FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, - false /* fgsRestrictionRecalculated */ - ); - } + final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(sr.appInfo.uid, fgsType); + if (fgsTypeInfo != null) { + // Update total runtime for the time-limited fgs type and mark it as timed out. + fgsTypeInfo.updateTotalRuntime(nowUptime); + fgsTypeInfo.setTimeLimitExceededAt(nowUptime); + + logFGSStateChangeLocked(sr, + FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT, + nowUptime > fgsTypeInfo.getFirstFgsStartUptime() + ? (int) (nowUptime - fgsTypeInfo.getFirstFgsStartUptime()) : 0, + FGS_STOP_REASON_UNKNOWN, + FGS_TYPE_POLICY_CHECK_UNKNOWN, + FOREGROUND_SERVICE_STATE_CHANGED__FGS_START_API__FGSSTARTAPI_NA, + false /* fgsRestrictionRecalculated */ + ); } try { @@ -3907,34 +3939,65 @@ public final class ActiveServices { Slog.w(TAG_SERVICE, "Exception from scheduleTimeoutServiceForType: " + e); } - if (android.app.Flags.introduceNewServiceOntimeoutCallback()) { - // 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) } + + synchronized (mAm) { + final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(sr.appInfo.uid, fgsType); + if (fgsTypeInfo != null) { + // Runtime is already updated when the service times out - if the app didn't + // stop the service, decrement the number of parallel running services here. + fgsTypeInfo.decNumParallelServices(); + } + } + final String reason = "A foreground service of type " + 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; + } + 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(); - Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); - traceInstant("FGS ANR: ", sr); - mAm.appNotResponding(sr.app, tr); + 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. + // 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. + } } } @@ -3951,7 +4014,6 @@ public final class ActiveServices { private void stopServiceAndUpdateAllowlistManagerLocked(ServiceRecord service) { maybeStopShortFgsTimeoutLocked(service); - maybeStopFgsTimeoutLocked(service); final ProcessServiceRecord psr = service.app.mServices; psr.stopService(service); psr.updateBoundClientUids(); @@ -6237,7 +6299,6 @@ public final class ActiveServices { Slog.w(TAG_SERVICE, "Short FGS brought down without stopping: " + r); maybeStopShortFgsTimeoutLocked(r); } - maybeStopFgsTimeoutLocked(r); // Report to all of the connections that the service is no longer // available. @@ -6362,7 +6423,6 @@ public final class ActiveServices { final boolean exitingFg = r.isForeground; if (exitingFg) { maybeStopShortFgsTimeoutLocked(r); - maybeStopFgsTimeoutLocked(r); decActiveForegroundAppLocked(smap, r); synchronized (mAm.mProcessStats.mLock) { ServiceState stracker = r.getTracker(); 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 1b59c1829d4c..4c8f41608f46 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -372,6 +372,8 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.server.ServerProtoEnums; import android.sysprop.InitProperties; +import android.system.Os; +import android.system.OsConstants; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.style.SuggestionSpan; @@ -598,6 +600,9 @@ public class ActivityManagerService extends IActivityManager.Stub // as one line, but close enough for now. static final int RESERVED_BYTES_PER_LOGCAT_LINE = 100; + // How many seconds should the system wait before terminating the spawned logcat process. + static final int LOGCAT_TIMEOUT_SEC = 10; + // Necessary ApplicationInfo flags to mark an app as persistent static final int PERSISTENT_MASK = ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT; @@ -1670,7 +1675,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 +2046,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; } } @@ -4342,6 +4347,7 @@ public class ActivityManagerService extends IActivityManager.Stub mServices.bringDownDisabledPackageServicesLocked( packageName, null, userId, false, true, true); + mServices.onUidRemovedLocked(uid); if (mBooted) { mAtmInternal.resumeTopActivities(true); @@ -4372,9 +4378,10 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.w(TAG, "Can't force stop all processes of all users, that is insane!"); } + final int uid = getPackageManagerInternal().getPackageUid(packageName, + MATCH_DEBUG_TRIAGED_MISSING | MATCH_ANY_USER, UserHandle.USER_SYSTEM); if (appId < 0 && packageName != null) { - appId = UserHandle.getAppId(getPackageManagerInternal().getPackageUid(packageName, - MATCH_DEBUG_TRIAGED_MISSING | MATCH_ANY_USER, UserHandle.USER_SYSTEM)); + appId = UserHandle.getAppId(uid); } boolean didSomething; @@ -4418,6 +4425,7 @@ public class ActivityManagerService extends IActivityManager.Stub } didSomething = true; } + mServices.onUidRemovedLocked(uid); if (packageName == null) { // Remove all sticky broadcasts from this user. @@ -4778,7 +4786,7 @@ public class ActivityManagerService extends IActivityManager.Stub checkTime(startTime, "attachApplicationLocked: immediately before bindApplication"); bindApplicationTimeMillis = SystemClock.uptimeMillis(); - bindApplicationTimeNanos = SystemClock.elapsedRealtimeNanos(); + bindApplicationTimeNanos = SystemClock.uptimeNanos(); mAtmInternal.preBindApplication(app.getWindowProcessController()); final ActiveInstrumentation instr2 = app.getActiveInstrumentation(); if (mPlatformCompat != null) { @@ -4848,7 +4856,8 @@ public class ActivityManagerService extends IActivityManager.Stub app.setBindApplicationTime(bindApplicationTimeMillis); mProcessList.getAppStartInfoTracker() - .reportBindApplicationTimeNanos(app, bindApplicationTimeNanos); + .addTimestampToStart(app, bindApplicationTimeNanos, + ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION); // Make app active after binding application or client may be running requests (e.g // starting activities) before it is ready. @@ -5581,32 +5590,30 @@ public class ActivityManagerService extends IActivityManager.Stub // security checking for it above. userId = UserHandle.USER_CURRENT; } - try { - if (owningUid != 0 && owningUid != SYSTEM_UID) { - final int uid = AppGlobals.getPackageManager().getPackageUid(packageName, - MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(owningUid)); - if (!UserHandle.isSameApp(owningUid, uid)) { - String msg = "Permission Denial: getIntentSender() from pid=" - + Binder.getCallingPid() - + ", uid=" + owningUid - + ", (need uid=" + uid + ")" - + " is not allowed to send as package " + packageName; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - } - if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { - return mAtmInternal.getIntentSender(type, packageName, featureId, owningUid, - userId, token, resultWho, requestCode, intents, resolvedTypes, flags, - bOptions); + if (owningUid != 0 && owningUid != SYSTEM_UID) { + if (!getPackageManagerInternal().isSameApp( + packageName, + MATCH_DEBUG_TRIAGED_MISSING, + owningUid, + UserHandle.getUserId(owningUid))) { + String msg = "Permission Denial: getIntentSender() from pid=" + + Binder.getCallingPid() + + ", uid=" + owningUid + + " is not allowed to send as package " + packageName; + Slog.w(TAG, msg); + throw new SecurityException(msg); } - return mPendingIntentController.getIntentSender(type, packageName, featureId, - owningUid, userId, token, resultWho, requestCode, intents, resolvedTypes, - flags, bOptions); - } catch (RemoteException e) { - throw new SecurityException(e); } + + if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { + return mAtmInternal.getIntentSender(type, packageName, featureId, owningUid, + userId, token, resultWho, requestCode, intents, resolvedTypes, flags, + bOptions); + } + return mPendingIntentController.getIntentSender(type, packageName, featureId, + owningUid, userId, token, resultWho, requestCode, intents, resolvedTypes, + flags, bOptions); } @Override @@ -9904,6 +9911,13 @@ public class ActivityManagerService extends IActivityManager.Stub sb.append("ErrorId: ").append(errorId.toString()).append("\n"); } sb.append("Build: ").append(Build.FINGERPRINT).append("\n"); + + // If device is not using 4KB pages, add the PageSize + long pageSize = Os.sysconf(OsConstants._SC_PAGESIZE); + if (pageSize != 4096) { + sb.append("PageSize: ").append(pageSize).append("\n"); + } + if (Debug.isDebuggerConnected()) { sb.append("Debugger: Connected\n"); } @@ -9935,126 +9949,70 @@ public class ActivityManagerService extends IActivityManager.Stub // If process is null, we are being called from some internal code // and may be about to die -- run this synchronously. final boolean runSynchronously = process == null; - Thread worker = - new Thread("Error dump: " + dropboxTag) { - @Override - public void run() { - if (report != null) { - sb.append(report); - } - - String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag; - String maxBytesSetting = - Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag; - int lines = - Build.IS_USER - ? 0 - : Settings.Global.getInt( - mContext.getContentResolver(), logcatSetting, 0); - int dropboxMaxSize = - Settings.Global.getInt( - mContext.getContentResolver(), - maxBytesSetting, - DROPBOX_DEFAULT_MAX_SIZE); - - if (dataFile != null) { - // Attach the stack traces file to the report so collectors can load - // them - // by file if they have access. - sb.append(DATA_FILE_PATH_HEADER) - .append(dataFile.getAbsolutePath()) - .append('\n'); - - int maxDataFileSize = - dropboxMaxSize - - sb.length() - - lines * RESERVED_BYTES_PER_LOGCAT_LINE - - DATA_FILE_PATH_FOOTER.length(); - - if (maxDataFileSize > 0) { - // Inline dataFile contents if there is room. - try { - sb.append( - FileUtils.readTextFile( - dataFile, - maxDataFileSize, - "\n\n[[TRUNCATED]]\n")); - } catch (IOException e) { - Slog.e(TAG, "Error reading " + dataFile, e); - } - } - - // Always append the footer, even there wasn't enough space to inline - // the - // dataFile contents. - sb.append(DATA_FILE_PATH_FOOTER); - } - - if (crashInfo != null && crashInfo.stackTrace != null) { - sb.append(crashInfo.stackTrace); - } - - if (lines > 0 && !runSynchronously) { - sb.append("\n"); - - InputStreamReader input = null; - try { - java.lang.Process logcat = - new ProcessBuilder( - // Time out after 10s of inactivity, but - // kill logcat with SEGV - // so we can investigate why it didn't - // finish. - "/system/bin/timeout", - "-i", - "-s", - "SEGV", - "10s", - // Merge several logcat streams, and take - // the last N lines. - "/system/bin/logcat", - "-v", - "threadtime", - "-b", - "events", - "-b", - "system", - "-b", - "main", - "-b", - "crash", - "-t", - String.valueOf(lines)) - .redirectErrorStream(true) - .start(); - - try { - logcat.getOutputStream().close(); - } catch (IOException e) { - } - try { - logcat.getErrorStream().close(); - } catch (IOException e) { - } - input = new InputStreamReader(logcat.getInputStream()); - - int num; - char[] buf = new char[8192]; - while ((num = input.read(buf)) > 0) sb.append(buf, 0, num); - } catch (IOException e) { - Slog.e(TAG, "Error running logcat", e); - } finally { - if (input != null) - try { - input.close(); - } catch (IOException e) { - } - } + Thread worker = new Thread("Error dump: " + dropboxTag) { + @Override + public void run() { + if (report != null) { + sb.append(report); + } + + String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag; + String kerLogSetting = Settings.Global.ERROR_KERNEL_LOG_PREFIX + dropboxTag; + String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag; + int logcatLines = Build.IS_USER + ? 0 + : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0); + int kernelLogLines = Build.IS_USER + ? 0 + : Settings.Global.getInt(mContext.getContentResolver(), kerLogSetting, 0); + int dropboxMaxSize = Settings.Global.getInt( + mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE); + + if (dataFile != null) { + // Attach the stack traces file to the report so collectors can load them + // by file if they have access. + sb.append(DATA_FILE_PATH_HEADER) + .append(dataFile.getAbsolutePath()).append('\n'); + + int maxDataFileSize = dropboxMaxSize + - sb.length() + - logcatLines * RESERVED_BYTES_PER_LOGCAT_LINE + - kernelLogLines * RESERVED_BYTES_PER_LOGCAT_LINE + - DATA_FILE_PATH_FOOTER.length(); + + if (maxDataFileSize > 0) { + // Inline dataFile contents if there is room. + try { + sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize, + "\n\n[[TRUNCATED]]\n")); + } catch (IOException e) { + Slog.e(TAG, "Error reading " + dataFile, e); } + } + // Always append the footer, even there wasn't enough space to inline the + // dataFile contents. + sb.append(DATA_FILE_PATH_FOOTER); + } - dbox.addText(dropboxTag, sb.toString()); + if (crashInfo != null && crashInfo.stackTrace != null) { + sb.append(crashInfo.stackTrace); + } + boolean shouldAddLogs = logcatLines > 0 || kernelLogLines > 0; + if (!runSynchronously && shouldAddLogs) { + sb.append("\n"); + if (logcatLines > 0) { + fetchLogcatBuffers(sb, logcatLines, LOGCAT_TIMEOUT_SEC, + List.of("events", "system", "main", "crash")); } - }; + if (kernelLogLines > 0) { + fetchLogcatBuffers(sb, kernelLogLines, LOGCAT_TIMEOUT_SEC / 2, + List.of("kernel")); + } + } + + dbox.addText(dropboxTag, sb.toString()); + } + }; if (runSynchronously) { final int oldMask = StrictMode.allowThreadDiskWritesMask(); @@ -10317,6 +10275,67 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * Retrieves logs from specified logcat buffers and appends them to a StringBuilder + * in the supplied order. The method executes a logcat command to fetch specific + * log entries from the supplied buffers. + * + * @param sb the StringBuilder to append the logcat output to. + * @param lines the number of lines to retrieve. + * @param timeout the maximum allowed time in seconds for logcat to run before being terminated. + * @param buffers the list of log buffers from which to retrieve logs. + */ + private static void fetchLogcatBuffers(StringBuilder sb, int lines, + int timeout, List<String> buffers) { + + if (buffers.size() == 0 || lines <= 0 || timeout <= 0) { + return; + } + + List<String> command = new ArrayList<>(10 + (2 * buffers.size())); + // Time out after 10s of inactivity, but kill logcat with SEGV + // so we can investigate why it didn't finish. + command.add("/system/bin/timeout"); + command.add("-i"); + command.add("-s"); + command.add("SEGV"); + command.add(timeout + "s"); + + // Merge several logcat streams, and take the last N lines. + command.add("/system/bin/logcat"); + command.add("-v"); + // This adds a timestamp and thread info to each log line. + command.add("threadtime"); + for (String buffer : buffers) { + command.add("-b"); + command.add(buffer); + } + // Limit the output to the last N lines. + command.add("-t"); + command.add(String.valueOf(lines)); + + try { + java.lang.Process proc = + new ProcessBuilder(command).redirectErrorStream(true).start(); + + // Close the output stream immediately as we do not send input to the process. + try { + proc.getOutputStream().close(); + } catch (IOException e) { + } + + try (InputStreamReader reader = new InputStreamReader(proc.getInputStream())) { + char[] buffer = new char[8192]; + int numRead; + while ((numRead = reader.read(buffer, 0, buffer.length)) > 0) { + sb.append(buffer, 0, numRead); + } + } + } catch (IOException e) { + Slog.e(TAG, "Error running logcat", e); + } + } + + /** * Check if the calling process has the permission to dump given package, * throw SecurityException if it doesn't have the permission. * @@ -12322,8 +12341,8 @@ public class ActivityManagerService extends IActivityManager.Stub ProcessList.SYSTEM_ADJ, ProcessList.PERSISTENT_PROC_ADJ, ProcessList.PERSISTENT_SERVICE_ADJ, ProcessList.FOREGROUND_APP_ADJ, ProcessList.VISIBLE_APP_ADJ, - ProcessList.PERCEPTIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ, - ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, + ProcessList.PERCEPTIBLE_APP_ADJ, + ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ, ProcessList.PERCEPTIBLE_LOW_APP_ADJ, ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ, ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ, ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MIN_ADJ @@ -12331,7 +12350,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final String[] DUMP_MEM_OOM_LABEL = new String[] { "Native", "System", "Persistent", "Persistent Service", "Foreground", - "Visible", "Perceptible", "Perceptible Low", "Perceptible Medium", + "Visible", "Perceptible", "Perceptible Medium", "Perceptible Low", "Backup", "Heavy Weight", "A Services", "Home", "Previous", "B Services", "Cached" @@ -12339,7 +12358,7 @@ public class ActivityManagerService extends IActivityManager.Stub static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] { "native", "sys", "pers", "persvc", "fore", - "vis", "percept", "perceptl", "perceptm", + "vis", "percept", "perceptm", "perceptl", "backup", "heavy", "servicea", "home", "prev", "serviceb", "cached" @@ -14314,7 +14333,7 @@ public class ActivityManagerService extends IActivityManager.Stub // activity manager to announce its creation. public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId, @BackupDestination int backupDestination) { - long startTimeNs = SystemClock.elapsedRealtimeNanos(); + long startTimeNs = SystemClock.uptimeNanos(); if (DEBUG_BACKUP) { Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode + " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid() @@ -19413,7 +19432,6 @@ public class ActivityManagerService extends IActivityManager.Stub // If the process is known as top app, set a hint so when the process is // started, the top priority can be applied immediately to avoid cpu being // preempted by other processes before attaching the process of top app. - final long startTimeNs = SystemClock.elapsedRealtimeNanos(); HostingRecord hostingRecord = new HostingRecord(hostingType, hostingName, isTop); ProcessRecord rec = getProcessRecordLocked(processName, info.uid); @@ -19950,6 +19968,40 @@ public class ActivityManagerService extends IActivityManager.Stub return !ActivityManagerService.this.mThemeOverlayReadyUsers.contains(userId); } } + + @Override + public void addStartInfoTimestamp(int key, long timestampNs, int uid, int pid, + int userId) { + // For the simplification, we don't support USER_ALL nor USER_CURRENT here. + if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_CURRENT) { + throw new IllegalArgumentException("Unsupported userId"); + } + + mUserController.handleIncomingUser(pid, uid, userId, true, + ALLOW_NON_FULL, "addStartInfoTimestampSystem", null); + + addStartInfoTimestampInternal(key, timestampNs, userId, uid); + } + + @Override + public void killApplicationSync(String pkgName, int appId, int userId, + String reason, int exitInfoReason) { + if (pkgName == null) { + return; + } + // Make sure the uid is valid. + if (appId < 0) { + Slog.w(TAG, "Invalid appid specified for pkg : " + pkgName); + return; + } + synchronized (ActivityManagerService.this) { + ActivityManagerService.this.forceStopPackageLocked(pkgName, appId, + /* callerWillRestart= */ false, /*purgeCache= */ false, + /* doit= */ true, /* evenPersistent= */ false, + /* uninstalling= */ false, /* packageStateStopped= */ false, + userId, reason, exitInfoReason); + } + } } long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) { @@ -20266,7 +20318,7 @@ public class ActivityManagerService extends IActivityManager.Stub final int userId = UserHandle.getCallingUserId(); final long callingId = Binder.clearCallingIdentity(); try { - if (uid == -1) { + if (uid == INVALID_UID) { uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); } mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType, diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 5af9424a025c..bbd432340e8f 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; @@ -129,6 +130,7 @@ import com.android.server.am.nano.Capabilities; import com.android.server.am.nano.Capability; import com.android.server.am.nano.FrameworkCapability; import com.android.server.am.nano.VMCapability; +import com.android.server.am.nano.VMInfo; import com.android.server.compat.PlatformCompat; import com.android.server.pm.UserManagerInternal; import com.android.server.utils.Slogf; @@ -275,9 +277,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; @@ -459,6 +461,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return -1; } } + String vmName = System.getProperty("java.vm.name", "?"); + String vmVersion = System.getProperty("java.vm.version", "?"); if (outputAsProtobuf) { Capabilities capabilities = new Capabilities(); @@ -485,6 +489,11 @@ final class ActivityManagerShellCommand extends ShellCommand { capabilities.frameworkCapabilities[i] = cap; } + VMInfo vmInfo = new VMInfo(); + vmInfo.name = vmName; + vmInfo.version = vmVersion; + capabilities.vmInfo = vmInfo; + try { getRawOutputStream().write(Capabilities.toByteArray(capabilities)); } catch (IOException e) { @@ -504,6 +513,8 @@ final class ActivityManagerShellCommand extends ShellCommand { for (String capability : Debug.getFeatureList()) { pw.println("framework:" + capability); } + pw.println("vm_name:" + vmName); + pw.println("vm_version:" + vmVersion); } return 0; } @@ -1203,45 +1214,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 +1243,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 +4298,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]"); @@ -4552,10 +4546,9 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" 1: crop_windows"); pw.println(" 2: resizeable"); pw.println(" 3: resizeable_and_pipable"); - pw.println(" resize <TASK_ID> <LEFT,TOP,RIGHT,BOTTOM>"); - pw.println(" Makes sure <TASK_ID> is in a stack with the specified bounds."); - pw.println(" Forces the task to be resizeable and creates a stack if no existing stack"); - pw.println(" has the specified bounds."); + pw.println(" resize <TASK_ID> <LEFT> <TOP> <RIGHT> <BOTTOM>"); + pw.println(" The task is resized only if it is in multi-window windowing"); + pw.println(" mode or freeform windowing mode."); pw.println(" update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]"); pw.println(" Update the ApplicationInfo objects of the listed packages for <USER_ID>"); pw.println(" without restarting any processes."); diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index c5cad14ee3d7..f5f1928c7c72 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -2387,8 +2387,8 @@ public final class AppRestrictionController { // Limit the length of the free-form subReason string if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) { + Slog.e(TAG, "subReason is too long, truncating " + subReason); subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH); - Slog.e(TAG, "Subreason is too long, truncating: " + subReason); } // Log the restriction reason diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java index 376f65438e64..79a85182ac09 100644 --- a/services/core/java/com/android/server/am/AppStartInfoTracker.java +++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java @@ -74,6 +74,7 @@ import java.util.function.BiFunction; /** A class to manage all the {@link android.app.ApplicationStartInfo} records. */ public final class AppStartInfoTracker { private static final String TAG = TAG_WITH_CLASS_NAME ? "AppStartInfoTracker" : TAG_AM; + private static final boolean DEBUG = false; /** Interval of persisting the app start info to persistent storage. */ private static final long APP_START_INFO_PERSIST_INTERVAL = TimeUnit.MINUTES.toMillis(30); @@ -293,9 +294,11 @@ public final class AppStartInfoTracker { mInProgRecords.removeAt(index); return; } - info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); info.setLaunchMode(launchMode); - checkCompletenessAndCallback(info); + if (!android.app.Flags.appStartInfoTimestamps()) { + info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); + checkCompletenessAndCallback(info); + } } } @@ -423,14 +426,6 @@ public final class AppStartInfoTracker { } } - void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) { - if (!mEnabled) { - return; - } - addTimestampToStart(app, timeNs, - ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE); - } - /** * Helper functions for monitoring shell command. * > adb shell am start-info-detailed-monitoring [package-name] @@ -465,45 +460,22 @@ public final class AppStartInfoTracker { } } - /** Report a bind application timestamp to add to {@link ApplicationStartInfo}. */ - public void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) { - addTimestampToStart(app, timeNs, - ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION); - } - - void reportFirstFrameTimeNanos(ProcessRecord app, long timeNs) { - if (!mEnabled) { - return; - } - addTimestampToStart(app, timeNs, - ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME); - } - - void reportFullyDrawnTimeNanos(ProcessRecord app, long timeNs) { - if (!mEnabled) { - return; - } - addTimestampToStart(app, timeNs, - ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN); + void addTimestampToStart(ProcessRecord app, long timeNs, int key) { + addTimestampToStart(app.info.packageName, app.uid, timeNs, key); } - void reportFullyDrawnTimeNanos(String processName, int uid, long timeNs) { + void addTimestampToStart(String packageName, int uid, long timeNs, int key) { if (!mEnabled) { return; } - addTimestampToStart(processName, uid, timeNs, - ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN); - } - - private void addTimestampToStart(ProcessRecord app, long timeNs, int key) { - addTimestampToStart(app.info.packageName, app.uid, timeNs, key); - } - - void addTimestampToStart(String packageName, int uid, long timeNs, int key) { synchronized (mLock) { AppStartInfoContainer container = mData.get(packageName, uid); if (container == null) { // Record was not created, discard new data. + if (DEBUG) { + Slog.d(TAG, "No container found for package=" + packageName + " and uid=" + uid + + ". Discarding timestamp key=" + key + " val=" + timeNs); + } return; } container.addTimestampToStartLocked(key, timeNs); @@ -534,9 +506,9 @@ public final class AppStartInfoTracker { } /** - * Called whenever data is added to a {@link ApplicationStartInfo} object. Checks for - * completeness and triggers callback if a callback has been registered and the object - * is complete. + * Called whenever a potentially final piece of data is added to a {@link ApplicationStartInfo} + * object. Checks for completeness and triggers callback if a callback has been registered and + * the object is complete. */ private void checkCompletenessAndCallback(ApplicationStartInfo startInfo) { synchronized (mLock) { @@ -1119,13 +1091,46 @@ public final class AppStartInfoTracker { Long.compare(getStartTimestamp(b), getStartTimestamp(a))); } + /** + * Add the provided key/timestamp to the most recent start record, if it is currently + * accepting new timestamps. + * + * Will also update the start records startup state and trigger the completion listener when + * appropriate. + */ @GuardedBy("mLock") void addTimestampToStartLocked(int key, long timestampNs) { - int index = mInfos.size() - 1; - int startupState = mInfos.get(index).getStartupState(); - if (startupState == ApplicationStartInfo.STARTUP_STATE_STARTED - || key == ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) { - mInfos.get(index).addStartupTimestamp(key, timestampNs); + if (mInfos.isEmpty()) { + if (DEBUG) Slog.d(TAG, "No records to add to."); + return; + } + + // Records are sorted newest to oldest, grab record at index 0. + ApplicationStartInfo startInfo = mInfos.get(0); + int startupState = startInfo.getStartupState(); + + // If startup state is error then don't accept any further timestamps. + if (startupState == ApplicationStartInfo.STARTUP_STATE_ERROR) { + if (DEBUG) Slog.d(TAG, "Startup state is error, not accepting new timestamps."); + return; + } + + // If startup state is first frame drawn then only accept fully drawn timestamp. + if (startupState == ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN + && key != ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN) { + if (DEBUG) { + Slog.d(TAG, "Startup state is first frame drawn and timestamp is not fully " + + "drawn, not accepting new timestamps."); + } + return; + } + + startInfo.addStartupTimestamp(key, timestampNs); + + if (key == ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME + && android.app.Flags.appStartInfoTimestamps()) { + startInfo.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN); + checkCompletenessAndCallback(startInfo); } } diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 4f841497b201..7c0325e7376a 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -124,6 +124,7 @@ import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsDumpHelperImpl; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; +import com.android.server.power.stats.BluetoothPowerStatsProcessor; import com.android.server.power.stats.CpuPowerStatsProcessor; import com.android.server.power.stats.MobileRadioPowerStatsProcessor; import com.android.server.power.stats.PhoneCallPowerStatsProcessor; @@ -159,6 +160,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * All information we are collecting about things that can happen that impact @@ -409,26 +412,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel); final boolean resetOnUnplugAfterSignificantCharge = context.getResources().getBoolean( com.android.internal.R.bool.config_batteryStatsResetOnUnplugAfterSignificantCharge); - final long powerStatsThrottlePeriodCpu = context.getResources().getInteger( - com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodCpu); - final long powerStatsThrottlePeriodMobileRadio = context.getResources().getInteger( - com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodMobileRadio); - final long powerStatsThrottlePeriodWifi = context.getResources().getInteger( - com.android.internal.R.integer.config_defaultPowerStatsThrottlePeriodWifi); - mBatteryStatsConfig = + BatteryStatsImpl.BatteryStatsConfig.Builder batteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder() .setResetOnUnplugHighBatteryLevel(resetOnUnplugHighBatteryLevel) - .setResetOnUnplugAfterSignificantCharge(resetOnUnplugAfterSignificantCharge) - .setPowerStatsThrottlePeriodMillis( - BatteryConsumer.POWER_COMPONENT_CPU, - powerStatsThrottlePeriodCpu) - .setPowerStatsThrottlePeriodMillis( - BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, - powerStatsThrottlePeriodMobileRadio) - .setPowerStatsThrottlePeriodMillis( - BatteryConsumer.POWER_COMPONENT_WIFI, - powerStatsThrottlePeriodWifi) - .build(); + .setResetOnUnplugAfterSignificantCharge( + resetOnUnplugAfterSignificantCharge); + setPowerStatsThrottlePeriods(batteryStatsConfigBuilder, context.getResources().getString( + com.android.internal.R.string.config_powerStatsThrottlePeriods)); + mBatteryStatsConfig = batteryStatsConfigBuilder.build(); mPowerStatsUidResolver = new PowerStatsUidResolver(); mStats = new BatteryStatsImpl(mBatteryStatsConfig, Clock.SYSTEM_CLOCK, mMonotonicClock, systemDir, mHandler, this, this, mUserManagerUserInfoProvider, mPowerProfile, @@ -512,9 +503,40 @@ public final class BatteryStatsService extends IBatteryStats.Stub AggregatedPowerStatsConfig.STATE_PROCESS_STATE) .setProcessor( new WifiPowerStatsProcessor(mPowerProfile)); + + config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_BLUETOOTH) + .trackDeviceStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN) + .trackUidStates( + AggregatedPowerStatsConfig.STATE_POWER, + AggregatedPowerStatsConfig.STATE_SCREEN, + AggregatedPowerStatsConfig.STATE_PROCESS_STATE) + .setProcessor( + new BluetoothPowerStatsProcessor(mPowerProfile)); return config; } + private void setPowerStatsThrottlePeriods(BatteryStatsImpl.BatteryStatsConfig.Builder builder, + String configString) { + Matcher matcher = Pattern.compile("([^:]+):(\\d+)\\s*").matcher(configString); + while (matcher.find()) { + String powerComponentName = matcher.group(1); + long throttlePeriod; + try { + throttlePeriod = Long.parseLong(matcher.group(2)); + } catch (NumberFormatException nfe) { + throw new IllegalArgumentException( + "Invalid config_powerStatsThrottlePeriods format: " + configString); + } + if (powerComponentName.equals("*")) { + builder.setDefaultPowerStatsThrottlePeriodMillis(throttlePeriod); + } else { + builder.setPowerStatsThrottlePeriodMillis(powerComponentName, throttlePeriod); + } + } + } + /** * Creates an instance of BatteryStatsService and restores data from stored state. */ @@ -553,6 +575,12 @@ public final class BatteryStatsService extends IBatteryStats.Stub BatteryConsumer.POWER_COMPONENT_WIFI, Flags.streamlinedConnectivityBatteryStats()); + mStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + Flags.streamlinedConnectivityBatteryStats()); + mBatteryUsageStatsProvider.setPowerStatsExporterEnabled( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + Flags.streamlinedConnectivityBatteryStats()); + mWorker.systemServicesReady(); mStats.systemServicesReady(mContext); mCpuWakeupStats.systemServicesReady(); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index 4425a388e9e1..1379c9b5cb68 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -1021,6 +1021,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final boolean allowWhileBooting = (r.intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0; + long startTimeNs = SystemClock.uptimeNanos(); if (DEBUG_BROADCAST) logv("Scheduling " + r + " to cold " + queue); queue.app = mService.startProcessLocked(queue.processName, info, true, intentFlags, hostingRecord, zygotePolicyFlags, allowWhileBooting, false); @@ -1032,8 +1033,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } // TODO: b/335420031 - cache receiver intent to avoid multiple calls to getReceiverIntent. mService.mProcessList.getAppStartInfoTracker().handleProcessBroadcastStart( - SystemClock.elapsedRealtimeNanos(), queue.app, r.getReceiverIntent(receiver), - r.alarm /* isAlarm */); + startTimeNs, queue.app, r.getReceiverIntent(receiver), r.alarm /* isAlarm */); return false; } 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/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 28fd1977539c..a8b9e43bdae4 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -179,7 +179,7 @@ public class ContentProviderHelper { final int expectedUserId = userId; synchronized (mService) { long startTime = SystemClock.uptimeMillis(); - long startElapsedTimeNs = SystemClock.elapsedRealtimeNanos(); + long startTimeNs = SystemClock.uptimeNanos(); ProcessRecord r = null; if (caller != null) { @@ -587,7 +587,7 @@ public class ContentProviderHelper { firstLaunch, 0L /* TODO: stoppedDuration */); mService.mProcessList.getAppStartInfoTracker() - .handleProcessContentProviderStart(startElapsedTimeNs, proc); + .handleProcessContentProviderStart(startTimeNs, proc); } cpr.launchingApp = proc; mLaunchingProviders.add(cpr); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index a289dd16170d..8647750d510f 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2610,6 +2610,15 @@ public class OomAdjuster { } } + // Sandbox should be able to control audio only when bound client + // has this capability. + if ((cstate.getCurCapability() + & PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL) != 0) { + if (app.isSdkSandbox) { + capability |= PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; + } + } + if (couldRecurse && shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) { return false; } @@ -3268,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) { @@ -3276,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..a67af89ad39d 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; @@ -83,6 +84,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.ToIntFunction; /** * A modern implementation of the oom adjuster. @@ -270,11 +272,31 @@ public class OomAdjusterModernImpl extends OomAdjuster { // The last node besides the tail. private final ProcessRecordNode[] mLastNode; + private final ToIntFunction<ProcessRecord> mSlotFunction; + // Cache of the most important slot with a node in it. + private int mFirstPopulatedSlot = 0; + ProcessRecordNodes(@ProcessRecordNode.NodeType int type, int size) { mType = type; + final ToIntFunction<ProcessRecord> valueFunction; + switch (mType) { + case ProcessRecordNode.NODE_TYPE_PROC_STATE: + valueFunction = (proc) -> proc.mState.getCurProcState(); + mSlotFunction = (proc) -> processStateToSlot(proc.mState.getCurProcState()); + break; + case ProcessRecordNode.NODE_TYPE_ADJ: + valueFunction = (proc) -> proc.mState.getCurRawAdj(); + mSlotFunction = (proc) -> adjToSlot(proc.mState.getCurRawAdj()); + break; + default: + valueFunction = (proc) -> 0; + mSlotFunction = (proc) -> 0; + break; + } + mProcessRecordNodes = new LinkedProcessRecordList[size]; for (int i = 0; i < size; i++) { - mProcessRecordNodes[i] = new LinkedProcessRecordList(type); + mProcessRecordNodes[i] = new LinkedProcessRecordList(valueFunction); } mLastNode = new ProcessRecordNode[size]; resetLastNodes(); @@ -293,6 +315,11 @@ public class OomAdjusterModernImpl extends OomAdjuster { } void resetLastNodes() { + if (Flags.simplifyProcessTraversal()) { + // Last nodes are no longer used. Just reset instead. + reset(); + return; + } for (int i = 0; i < mProcessRecordNodes.length; i++) { mLastNode[i] = mProcessRecordNodes[i].getLastNodeBeforeTail(); } @@ -307,6 +334,36 @@ public class OomAdjusterModernImpl extends OomAdjuster { final ProcessRecordNode tail = mProcessRecordNodes[slot].TAIL; while (node != tail) { mTmpOomAdjusterArgs.mApp = node.mApp; + if (node.mApp == null) { + // TODO(b/336178916) - Temporary logging for root causing b/336178916. + StringBuilder sb = new StringBuilder(); + sb.append("Iterating null process during OomAdjuster traversal!!!\n"); + sb.append("Type:"); + switch (mType) { + case ProcessRecordNode.NODE_TYPE_PROC_STATE -> sb.append( + "NODE_TYPE_PROC_STATE"); + case ProcessRecordNode.NODE_TYPE_ADJ -> sb.append("NODE_TYPE_ADJ"); + default -> sb.append("UNKNOWN"); + } + sb.append(", Slot:"); + sb.append(slot); + sb.append("\nLAST:"); + ProcessRecordNode last = mLastNode[slot]; + if (last.mApp == null) { + sb.append("null"); + } else { + sb.append(last); + sb.append("\nSetProcState:"); + sb.append(last.mApp.getSetProcState()); + sb.append(", CurProcState:"); + sb.append(last.mApp.mState.getCurProcState()); + sb.append(", SetAdj:"); + sb.append(last.mApp.getSetAdj()); + sb.append(", CurRawAdj:"); + sb.append(last.mApp.mState.getCurRawAdj()); + } + Slog.wtfStack(TAG, sb.toString()); + } // Save the next before calling callback, since that may change the node.mNext. final ProcessRecordNode next = node.mNext; callback.accept(mTmpOomAdjusterArgs); @@ -324,6 +381,33 @@ public class OomAdjusterModernImpl extends OomAdjuster { } } + ProcessRecord poll() { + ProcessRecordNode node = null; + final int size = mProcessRecordNodes.length; + // Find the next node. + while (node == null && mFirstPopulatedSlot < size) { + node = mProcessRecordNodes[mFirstPopulatedSlot].poll(); + if (node == null) { + // This slot is now empty, move on to the next. + mFirstPopulatedSlot++; + } + } + if (node == null) return null; + return node.mApp; + } + + void offer(ProcessRecord proc) { + ProcessRecordNode node = proc.mLinkedNodes[mType]; + // Find which slot to add the node to. + final int newSlot = mSlotFunction.applyAsInt(proc); + if (newSlot < mFirstPopulatedSlot) { + // node is being added to a more important slot. + mFirstPopulatedSlot = newSlot; + } + node.unlink(); + mProcessRecordNodes[newSlot].offer(node); + } + int getNumberOfSlots() { return mProcessRecordNodes.length; } @@ -422,12 +506,35 @@ public class OomAdjusterModernImpl extends OomAdjuster { // Sentinel head/tail, to make bookkeeping work easier. final ProcessRecordNode HEAD = new ProcessRecordNode(null); final ProcessRecordNode TAIL = new ProcessRecordNode(null); - final @ProcessRecordNode.NodeType int mNodeType; + final ToIntFunction<ProcessRecord> mValueFunction; - LinkedProcessRecordList(@ProcessRecordNode.NodeType int nodeType) { + LinkedProcessRecordList(ToIntFunction<ProcessRecord> valueFunction) { HEAD.mNext = TAIL; TAIL.mPrev = HEAD; - mNodeType = nodeType; + mValueFunction = valueFunction; + } + + ProcessRecordNode poll() { + final ProcessRecordNode next = HEAD.mNext; + if (next == TAIL) return null; + next.unlink(); + return next; + } + + void offer(@NonNull ProcessRecordNode node) { + final int newValue = mValueFunction.applyAsInt(node.mApp); + + // Find the last node with less than or equal value to the new node. + ProcessRecordNode curNode = TAIL.mPrev; + while (curNode != HEAD && mValueFunction.applyAsInt(curNode.mApp) > newValue) { + curNode = curNode.mPrev; + } + + // Insert the new node after the found node. + node.mPrev = curNode; + node.mNext = curNode.mNext; + curNode.mNext.mPrev = node; + curNode.mNext = node; } void append(@NonNull ProcessRecordNode node) { @@ -521,6 +628,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 +665,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 +716,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); @@ -704,34 +833,50 @@ public class OomAdjusterModernImpl extends OomAdjuster { private void updateAdjSlotIfNecessary(ProcessRecord app, int prevRawAdj) { if (app.mState.getCurRawAdj() != prevRawAdj) { - final int slot = adjToSlot(app.mState.getCurRawAdj()); - final int prevSlot = adjToSlot(prevRawAdj); - if (slot != prevSlot && slot != ADJ_SLOT_INVALID) { - mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot); + if (Flags.simplifyProcessTraversal()) { + mProcessRecordAdjNodes.offer(app); + } else { + final int slot = adjToSlot(app.mState.getCurRawAdj()); + final int prevSlot = adjToSlot(prevRawAdj); + if (slot != prevSlot && slot != ADJ_SLOT_INVALID) { + mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot); + } } } } private void updateAdjSlot(ProcessRecord app, int prevRawAdj) { - final int slot = adjToSlot(app.mState.getCurRawAdj()); - final int prevSlot = adjToSlot(prevRawAdj); - mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot); + if (Flags.simplifyProcessTraversal()) { + mProcessRecordAdjNodes.offer(app); + } else { + final int slot = adjToSlot(app.mState.getCurRawAdj()); + final int prevSlot = adjToSlot(prevRawAdj); + mProcessRecordAdjNodes.moveAppTo(app, prevSlot, slot); + } } private void updateProcStateSlotIfNecessary(ProcessRecord app, int prevProcState) { if (app.mState.getCurProcState() != prevProcState) { - final int slot = processStateToSlot(app.mState.getCurProcState()); - final int prevSlot = processStateToSlot(prevProcState); - if (slot != prevSlot) { - mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot); + if (Flags.simplifyProcessTraversal()) { + mProcessRecordProcStateNodes.offer(app); + } else { + final int slot = processStateToSlot(app.mState.getCurProcState()); + final int prevSlot = processStateToSlot(prevProcState); + if (slot != prevSlot) { + mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot); + } } } } private void updateProcStateSlot(ProcessRecord app, int prevProcState) { - final int slot = processStateToSlot(app.mState.getCurProcState()); - final int prevSlot = processStateToSlot(prevProcState); - mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot); + if (Flags.simplifyProcessTraversal()) { + mProcessRecordProcStateNodes.offer(app); + } else { + final int slot = processStateToSlot(app.mState.getCurProcState()); + final int prevSlot = processStateToSlot(prevProcState); + mProcessRecordProcStateNodes.moveAppTo(app, prevSlot, slot); + } } @Override @@ -809,8 +954,15 @@ public class OomAdjusterModernImpl extends OomAdjuster { // Compute initial values, the procState and adj priority queues will be populated here. computeOomAdjLSP(app, UNKNOWN_ADJ, topApp, true, now, false, false, oomAdjReason, false); - updateProcStateSlot(app, prevProcState); - updateAdjSlot(app, prevAdj); + + if (Flags.simplifyProcessTraversal()) { + // Just add to the procState priority queue. The adj priority queue should be + // empty going into the traversal step. + mProcessRecordProcStateNodes.offer(app); + } else { + updateProcStateSlot(app, prevProcState); + updateAdjSlot(app, prevAdj); + } } // Set adj last nodes now, this way a process will only be reevaluated during the adj node @@ -828,14 +980,32 @@ public class OomAdjusterModernImpl extends OomAdjuster { */ @GuardedBy({"mService", "mProcLock"}) private void computeConnectionsLSP() { - // 1st pass, scan each slot in the procstate node list. - for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) { - mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer); - } + if (Flags.simplifyProcessTraversal()) { + // 1st pass, iterate all nodes in order of procState importance. + ProcessRecord proc = mProcessRecordProcStateNodes.poll(); + while (proc != null) { + mTmpOomAdjusterArgs.mApp = proc; + mComputeConnectionsConsumer.accept(mTmpOomAdjusterArgs); + proc = mProcessRecordProcStateNodes.poll(); + } + + // 2st pass, iterate all nodes in order of procState importance. + proc = mProcessRecordAdjNodes.poll(); + while (proc != null) { + mTmpOomAdjusterArgs.mApp = proc; + mComputeConnectionsConsumer.accept(mTmpOomAdjusterArgs); + proc = mProcessRecordAdjNodes.poll(); + } + } else { + // 1st pass, scan each slot in the procstate node list. + for (int i = 0, end = mProcessRecordProcStateNodes.size() - 1; i < end; i++) { + mProcessRecordProcStateNodes.forEachNewNode(i, mComputeConnectionsConsumer); + } - // 2nd pass, scan each slot in the adj node list. - for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) { - mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer); + // 2nd pass, scan each slot in the adj node list. + for (int i = 0, end = mProcessRecordAdjNodes.size() - 1; i < end; i++) { + mProcessRecordAdjNodes.forEachNewNode(i, mComputeConnectionsConsumer); + } } } @@ -874,7 +1044,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 +1084,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 +1098,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(); @@ -939,8 +1134,14 @@ public class OomAdjusterModernImpl extends OomAdjuster { args.mApp = reachable; computeOomAdjIgnoringReachablesLSP(args); - updateProcStateSlot(reachable, prevProcState); - updateAdjSlot(reachable, prevAdj); + if (Flags.simplifyProcessTraversal()) { + // Just add to the procState priority queue. The adj priority queue should be + // empty going into the traversal step. + mProcessRecordProcStateNodes.offer(reachable); + } else { + updateProcStateSlot(reachable, prevProcState); + updateAdjSlot(reachable, prevAdj); + } } } @@ -948,9 +1149,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 +1161,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 +1243,7 @@ public class OomAdjusterModernImpl extends OomAdjuster { } else { client = cr.binding.client; } + if (client == null || client == app) continue; connectionConsumer.accept(cr, client); } } @@ -1053,4 +1258,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/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index da45a7727faf..8d7a1c9f8228 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -18,6 +18,10 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.START_SUCCESS; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -389,13 +393,20 @@ public final class PendingIntentRecord extends IIntentSender.Stub { private static BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCaller( @Nullable Bundle options, int callingUid, @Nullable String callingPackage) { - if (options == null || !options.containsKey( - ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED)) { + if (options == null) { return getDefaultBackgroundStartPrivileges(callingUid, callingPackage); } - return options.getBoolean(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED) - ? BackgroundStartPrivileges.ALLOW_BAL - : BackgroundStartPrivileges.NONE; + switch (options.getInt(ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, + MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)) { + case MODE_BACKGROUND_ACTIVITY_START_DENIED: + return BackgroundStartPrivileges.NONE; + case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED: + return getDefaultBackgroundStartPrivileges(callingUid, callingPackage); + case MODE_BACKGROUND_ACTIVITY_START_ALLOWED: + case MODE_BACKGROUND_ACTIVITY_START_COMPAT: + default: + return BackgroundStartPrivileges.ALLOW_BAL; + } } /** diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 8eca4fc0d2ff..218434049869 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -690,10 +690,14 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN private long mTimeLimitExceededAt = Long.MIN_VALUE; @UptimeMillisLong private long mTotalRuntime = 0; + private int mNumParallelServices = 0; - TimeLimitedFgsInfo(@UptimeMillisLong long startTime) { - mFirstFgsStartUptime = startTime; - mFirstFgsStartRealtime = SystemClock.elapsedRealtime(); + public void noteFgsFgsStart(@UptimeMillisLong long startTime) { + mNumParallelServices++; + if (mNumParallelServices == 1) { + mFirstFgsStartUptime = startTime; + mFirstFgsStartRealtime = SystemClock.elapsedRealtime(); + } mLastFgsStartTime = startTime; } @@ -707,17 +711,23 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN return mFirstFgsStartRealtime; } - public void setLastFgsStartTime(@UptimeMillisLong long startTime) { - mLastFgsStartTime = startTime; - } - @UptimeMillisLong public long getLastFgsStartTime() { return mLastFgsStartTime; } - public void updateTotalRuntime() { - mTotalRuntime += SystemClock.uptimeMillis() - mLastFgsStartTime; + public void decNumParallelServices() { + if (mNumParallelServices > 0) { + mNumParallelServices--; + } + if (mNumParallelServices == 0) { + mLastFgsStartTime = 0; + } + } + + public void updateTotalRuntime(@UptimeMillisLong long nowUptime) { + mTotalRuntime += nowUptime - mLastFgsStartTime; + mLastFgsStartTime = nowUptime; } @UptimeMillisLong @@ -735,6 +745,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } public void reset() { + mNumParallelServices = 0; mFirstFgsStartUptime = 0; mFirstFgsStartRealtime = 0; mLastFgsStartTime = 0; @@ -1872,8 +1883,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN /** * Called when a time-limited FGS starts. */ - public TimeLimitedFgsInfo createTimeLimitedFgsInfo(@UptimeMillisLong long nowUptime) { - return new TimeLimitedFgsInfo(nowUptime); + public TimeLimitedFgsInfo createTimeLimitedFgsInfo() { + return new TimeLimitedFgsInfo(); } /** diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 9b83ede09da4..032093b91746 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; @@ -27,9 +29,19 @@ import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; 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; @@ -128,6 +140,7 @@ public class SettingsToPropertiesMapper { "aoc", "app_widgets", "arc_next", + "art_mainline", "avic", "biometrics", "biometrics_framework", @@ -144,6 +157,7 @@ public class SettingsToPropertiesMapper { "car_telemetry", "codec_fwk", "companion", + "com_android_adbd", "content_protection", "context_hub", "core_experiments_team_internal", @@ -184,6 +198,7 @@ public class SettingsToPropertiesMapper { "pmw", "power", "preload_safety", + "printing", "privacy_infra_policy", "resource_manager", "responsible_apis", @@ -223,6 +238,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; @@ -248,11 +265,11 @@ public class SettingsToPropertiesMapper { Uri settingUri = Settings.Global.getUriFor(globalSetting); String propName = makePropertyName(GLOBAL_SETTINGS_CATEGORY, globalSetting); if (settingUri == null) { - log("setting uri is null for globalSetting " + globalSetting); + logErr("setting uri is null for globalSetting " + globalSetting); continue; } if (propName == null) { - log("invalid prop name for globalSetting " + globalSetting); + logErr("invalid prop name for globalSetting " + globalSetting); continue; } @@ -280,7 +297,7 @@ public class SettingsToPropertiesMapper { for (String key : properties.getKeyset()) { String propertyName = makePropertyName(scope, key); if (propertyName == null) { - log("unable to construct system property for " + scope + "/" + logErr("unable to construct system property for " + scope + "/" + key); return; } @@ -292,7 +309,7 @@ public class SettingsToPropertiesMapper { // sys prop slot can be removed. String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key); if (aconfigPropertyName == null) { - log("unable to construct system property for " + scope + "/" + logErr("unable to construct system property for " + scope + "/" + key); return; } @@ -310,7 +327,7 @@ public class SettingsToPropertiesMapper { for (String key : properties.getKeyset()) { String aconfigPropertyName = makeAconfigFlagPropertyName(scope, key); if (aconfigPropertyName == null) { - log("unable to construct system property for " + scope + "/" + logErr("unable to construct system property for " + scope + "/" + key); return; } @@ -325,30 +342,191 @@ public class SettingsToPropertiesMapper { AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties properties) -> { - HashMap<String, HashMap<String, String>> propsToStage = - getStagedFlagsWithValueChange(properties); + for (String flagName : properties.getKeyset()) { + String flagValue = properties.getString(flagName, null); + if (flagName == null || flagValue == null) { + continue; + } - for (HashMap.Entry<String, HashMap<String, String>> entry : propsToStage.entrySet()) { - String actualNamespace = entry.getKey(); - HashMap<String, String> flagValuesToStage = entry.getValue(); + int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); + if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { + logErr("invalid staged flag: " + flagName); + continue; + } - for (String flagName : flagValuesToStage.keySet()) { - String stagedValue = flagValuesToStage.get(flagName); + String actualNamespace = flagName.substring(0, idx); + String actualFlagName = flagName.substring(idx+1); String propertyName = "next_boot." + makeAconfigFlagPropertyName( - actualNamespace, flagName); + actualNamespace, actualFlagName); - if (!propertyName.matches(SYSTEM_PROPERTY_VALID_CHARACTERS_REGEX) - || propertyName.contains(SYSTEM_PROPERTY_INVALID_SUBSTRING)) { - log("unable to construct system property for " + actualNamespace - + "/" + flagName); - continue; - } + setProperty(propertyName, flagValue); + } - setProperty(propertyName, stagedValue); + // send prop stage request to new storage + if (enableAconfigStorageDaemon()) { + stageFlagsInNewStorage(properties); + } + + }); + + // 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 requests: request proto output stream + * @return aconfigd socket return as proto input stream + */ + static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) { + // connect to aconfigd socket + LocalSocket client = new LocalSocket(); + try{ + client.connect(new LocalSocketAddress( + "aconfigd", LocalSocketAddress.Namespace.RESERVED)); + Slog.d(TAG, "connected to aconfigd socket"); + } catch (IOException ioe) { + logErr("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) { + logErr("failed to get local socket iostreams", ioe); + return null; + } + + // send requests + try { + byte[] requests_bytes = requests.getBytes(); + outputStream.writeInt(requests_bytes.length); + outputStream.write(requests_bytes, 0, requests_bytes.length); + Slog.d(TAG, "flag override requests sent to aconfigd"); + } catch (IOException ioe) { + logErr("failed to send requests to aconfigd", ioe); + return null; + } + + // read return + try { + int num_bytes = inputStream.readInt(); + ProtoInputStream returns = new ProtoInputStream(inputStream); + Slog.d(TAG, "received " + num_bytes + " bytes back from aconfigd"); + return returns; + } catch (IOException ioe) { + logErr("failed to read requests return from aconfigd", ioe); + return null; + } + } + + /** + * serialize a flag override request + * @param proto + */ + static void writeFlagOverrideRequest( + ProtoOutputStream proto, String packageName, String flagName, String flagValue, + boolean isLocal) { + long msgsToken = proto.start(StorageRequestMessages.MSGS); + long msgToken = proto.start(StorageRequestMessage.FLAG_OVERRIDE_MESSAGE); + proto.write(StorageRequestMessage.FlagOverrideMessage.PACKAGE_NAME, packageName); + proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_NAME, flagName); + proto.write(StorageRequestMessage.FlagOverrideMessage.FLAG_VALUE, flagValue); + proto.write(StorageRequestMessage.FlagOverrideMessage.IS_LOCAL, isLocal); + proto.end(msgToken); + proto.end(msgsToken); + } + + /** + * deserialize a flag input proto stream and log + * @param proto + */ + static void parseAndLogAconfigdReturn(ProtoInputStream proto) throws IOException { + while (true) { + switch (proto.nextField()) { + case (int) StorageReturnMessages.MSGS: + long msgsToken = proto.start(StorageReturnMessages.MSGS); + switch (proto.nextField()) { + case (int) StorageReturnMessage.FLAG_OVERRIDE_MESSAGE: + Slog.d(TAG, "successfully handled override requests"); + long msgToken = proto.start(StorageReturnMessage.FLAG_OVERRIDE_MESSAGE); + proto.end(msgToken); + break; + case (int) StorageReturnMessage.ERROR_MESSAGE: + String errmsg = proto.readString(StorageReturnMessage.ERROR_MESSAGE); + Slog.d(TAG, "override request failed: " + errmsg); + break; + case ProtoInputStream.NO_MORE_FIELDS: + break; + default: + logErr("invalid message type, expecting only flag override return or error message"); + break; } + proto.end(msgsToken); + break; + case ProtoInputStream.NO_MORE_FIELDS: + return; + default: + logErr("invalid message type, expect storage return message"); + break; + } + } + } + + /** + * apply flag local override in aconfig new storage + * @param props + */ + static void setLocalOverridesInNewStorage(DeviceConfig.Properties props) { + int num_requests = 0; + ProtoOutputStream requests = new ProtoOutputStream(); + 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) { + logErr("invalid local flag override: " + flagName); + continue; + } + String actualNamespace = flagName.substring(0, idx); + String fullFlagName = flagName.substring(idx+1); + idx = fullFlagName.lastIndexOf("."); + if (idx == -1) { + logErr("invalid flag name: " + fullFlagName); + continue; + } + String packageName = fullFlagName.substring(0, idx); + String realFlagName = fullFlagName.substring(idx+1); + writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, true); + ++num_requests; + } + + if (num_requests == 0) { + return; + } - }); + // send requests to aconfigd and obtain the return byte buffer + ProtoInputStream returns = sendAconfigdRequests(requests); + + // deserialize back using proto input stream + try { + parseAndLogAconfigdReturn(returns); + } catch (IOException ioe) { + logErr("failed to parse aconfigd return", ioe); + } } public static SettingsToPropertiesMapper start(ContentResolver contentResolver) { @@ -391,7 +569,7 @@ public class SettingsToPropertiesMapper { for (String property_name : property_names) { String[] segments = property_name.split("\\."); if (segments.length < 3) { - log("failed to extract category name from property " + property_name); + logErr("failed to extract category name from property " + property_name); continue; } categories.add(segments[2]); @@ -419,6 +597,56 @@ public class SettingsToPropertiesMapper { return propertyName; } + + /** + * stage flags in aconfig new storage + * @param propsToStage + */ + @VisibleForTesting + static void stageFlagsInNewStorage(DeviceConfig.Properties props) { + // write aconfigd requests proto to proto output stream + int num_requests = 0; + ProtoOutputStream requests = new ProtoOutputStream(); + 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) { + logErr("invalid local flag override: " + flagName); + continue; + } + String actualNamespace = flagName.substring(0, idx); + String fullFlagName = flagName.substring(idx+1); + + idx = fullFlagName.lastIndexOf("."); + if (idx == -1) { + logErr("invalid flag name: " + fullFlagName); + continue; + } + String packageName = fullFlagName.substring(0, idx); + String realFlagName = fullFlagName.substring(idx+1); + writeFlagOverrideRequest(requests, packageName, realFlagName, flagValue, false); + ++num_requests; + } + + if (num_requests == 0) { + return; + } + + // send requests to aconfigd and obtain the return + ProtoInputStream returns = sendAconfigdRequests(requests); + + // deserialize back using proto input stream + try { + parseAndLogAconfigdReturn(returns); + } catch (IOException ioe) { + logErr("failed to parse aconfigd return", ioe); + } + } + /** * system property name constructing rule for aconfig flags: * "persist.device_config.aconfig_flags.[category_name].[flag_name]". @@ -441,63 +669,6 @@ public class SettingsToPropertiesMapper { return propertyName; } - /** - * Get the flags that need to be staged in sys prop, only these with a real value - * change needs to be staged in sys prop. Otherwise, the flag stage is useless and - * create performance problem at sys prop side. - * @param properties - * @return a hash map of namespace name to actual flags to stage - */ - @VisibleForTesting - static HashMap<String, HashMap<String, String>> getStagedFlagsWithValueChange( - DeviceConfig.Properties properties) { - - // sort flags by actual namespace of the flag - HashMap<String, HashMap<String, String>> stagedProps = new HashMap<>(); - for (String flagName : properties.getKeyset()) { - int idx = flagName.indexOf(NAMESPACE_REBOOT_STAGING_DELIMITER); - if (idx == -1 || idx == flagName.length() - 1 || idx == 0) { - log("invalid staged flag: " + flagName); - continue; - } - String actualNamespace = flagName.substring(0, idx); - String actualFlagName = flagName.substring(idx+1); - HashMap<String, String> flagStagedValues = stagedProps.get(actualNamespace); - if (flagStagedValues == null) { - flagStagedValues = new HashMap<String, String>(); - stagedProps.put(actualNamespace, flagStagedValues); - } - flagStagedValues.put(actualFlagName, properties.getString(flagName, null)); - } - - // for each namespace, find flags with real flag value change - HashMap<String, HashMap<String, String>> propsToStage = new HashMap<>(); - for (HashMap.Entry<String, HashMap<String, String>> entry : stagedProps.entrySet()) { - String actualNamespace = entry.getKey(); - HashMap<String, String> flagStagedValues = entry.getValue(); - Map<String, String> flagCurrentValues = Settings.Config.getStrings( - actualNamespace, new ArrayList<String>(flagStagedValues.keySet())); - - HashMap<String, String> flagsToStage = new HashMap<>(); - 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 && !stagedValue.equalsIgnoreCase(currentValue)) { - flagsToStage.put(flagName, stagedValue); - } - } - - if (!flagsToStage.isEmpty()) { - propsToStage.put(actualNamespace, flagsToStage); - } - } - - return propsToStage; - } - private void setProperty(String key, String value) { // Check if need to clear the property if (value == null) { @@ -508,7 +679,7 @@ public class SettingsToPropertiesMapper { } value = ""; } else if (value.length() > SYSTEM_PROPERTY_MAX_LENGTH) { - log("key=" + key + " value=" + value + " exceeds system property max length."); + logErr("key=" + key + " value=" + value + " exceeds system property max length."); return; } @@ -518,11 +689,11 @@ public class SettingsToPropertiesMapper { // Failure to set a property can be caused by SELinux denial. This usually indicates // that the property wasn't allowlisted in sepolicy. // No need to report it on all user devices, only on debug builds. - log("Unable to set property " + key + " value '" + value + "'", e); + logErr("Unable to set property " + key + " value '" + value + "'", e); } } - private static void log(String msg, Exception e) { + private static void logErr(String msg, Exception e) { if (Build.IS_DEBUGGABLE) { Slog.wtf(TAG, msg, e); } else { @@ -530,7 +701,7 @@ public class SettingsToPropertiesMapper { } } - private static void log(String msg) { + private static void logErr(String msg) { if (Build.IS_DEBUGGABLE) { Slog.wtf(TAG, msg); } else { @@ -548,7 +719,7 @@ public class SettingsToPropertiesMapper { br.close(); } catch (IOException ioe) { - log("failed to read file " + RESET_RECORD_FILE_PATH, ioe); + logErr("failed to read file " + RESET_RECORD_FILE_PATH, ioe); } return content; } diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index fb63ec619918..afde4f71a95f 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -116,3 +116,21 @@ 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" +} + +flag { + name: "simplify_process_traversal" + namespace: "backstage_power" + description: "Simplify the OomAdjuster's process traversal mechanism." + bug: "336178916" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index ce410796137b..e066c23d4d59 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -572,12 +572,8 @@ public final class AppHibernationService extends SystemService { packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, true, ActivityManager.RESTRICTION_REASON_DORMANT, null, /* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L); - } else { - mIActivityManager.noteAppRestrictionEnabled( - packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, - false, ActivityManager.RESTRICTION_REASON_USAGE, null, - 0L); } + // No need to log the unhibernate case as an unstop is logged already in ActivityMS } catch (RemoteException e) { Slog.e(TAG, "Couldn't set restriction state change"); } diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 798aaee58a8e..147c8d7853d4 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; @@ -1102,18 +1103,19 @@ public class AppOpsService extends IAppOpsService.Stub { if (onModeChangedListeners == null) { continue; } + onModeChangedListeners = new ArraySet<>(onModeChangedListeners); } for (int i = 0; i < changedUids.length; i++) { final int changedUid = changedUids[i]; final String changedPkg = changedPkgs[i]; // We trust packagemanager to insert matching uid and packageNames in the // extras - Set<String> devices; + Set<String> devices = new ArraySet<>(); + devices.add(PERSISTENT_DEVICE_ID_DEFAULT); + if (mVirtualDeviceManagerInternal != null) { - devices = mVirtualDeviceManagerInternal.getAllPersistentDeviceIds(); - } else { - devices = new ArraySet<>(); - devices.add(PERSISTENT_DEVICE_ID_DEFAULT); + devices.addAll( + mVirtualDeviceManagerInternal.getAllPersistentDeviceIds()); } for (String device: devices) { notifyOpChanged(onModeChangedListeners, code, changedUid, changedPkg, @@ -1254,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); @@ -1418,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; @@ -1505,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++) { @@ -1529,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. */ @@ -2522,12 +2609,10 @@ public class AppOpsService extends IAppOpsService.Stub { ArrayList<ChangeRec> reports = ent.getValue(); for (int i=0; i<reports.size(); i++) { ChangeRec rep = reports.get(i); - Set<String> devices; + Set<String> devices = new ArraySet<>(); + devices.add(PERSISTENT_DEVICE_ID_DEFAULT); if (mVirtualDeviceManagerInternal != null) { - devices = mVirtualDeviceManagerInternal.getAllPersistentDeviceIds(); - } else { - devices = new ArraySet<>(); - devices.add(PERSISTENT_DEVICE_ID_DEFAULT); + devices.addAll(mVirtualDeviceManagerInternal.getAllPersistentDeviceIds()); } for (String device: devices) { mHandler.sendMessage(PooledLambda.obtainMessage( @@ -2699,7 +2784,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); } @@ -2909,10 +2994,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); @@ -2949,7 +3036,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, @@ -2967,9 +3055,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 @@ -3020,14 +3108,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 { @@ -3158,8 +3246,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, @@ -3525,9 +3614,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. */ @@ -3565,18 +3654,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))) { @@ -3618,7 +3721,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)) { @@ -3630,7 +3733,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)) { @@ -3639,9 +3742,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) { @@ -3651,9 +3755,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); @@ -3748,13 +3853,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) { @@ -4752,8 +4857,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 { @@ -4943,7 +5048,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); @@ -6396,12 +6501,13 @@ public class AppOpsService extends IAppOpsService.Stub { } private void notifyWatchersOnDefaultDevice(int code, int uid) { - final ArraySet<OnOpModeChangedListener> modeChangedListenerSet; + ArraySet<OnOpModeChangedListener> modeChangedListenerSet; synchronized (this) { modeChangedListenerSet = mOpModeWatchers.get(code); if (modeChangedListenerSet == null) { return; } + modeChangedListenerSet = new ArraySet<>(modeChangedListenerSet); } notifyOpChanged(modeChangedListenerSet, code, uid, null, PERSISTENT_DEVICE_ID_DEFAULT); } 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/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index da528a2591a1..1dc1846fbb96 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -274,8 +274,11 @@ public class AudioDeviceBroker { } /*package*/ void setBluetoothA2dpOn_Async(boolean on, String source) { - mBluetoothA2dpEnabled.set(on); - sendLMsgNoDelay(MSG_L_SET_FORCE_BT_A2DP_USE, SENDMSG_REPLACE, source); + boolean wasOn = mBluetoothA2dpEnabled.getAndSet(on); + // do not mute music if we do not anticipate a change in A2DP ON state + sendLMsgNoDelay(wasOn == on + ? MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE : MSG_L_SET_FORCE_BT_A2DP_USE, + SENDMSG_REPLACE, source); } /** @@ -1436,7 +1439,6 @@ public class AudioDeviceBroker { sendMsgNoDelay(MSG_BROADCAST_AUDIO_BECOMING_NOISY, SENDMSG_REPLACE); } - @GuardedBy("mDeviceStateLock") /*package*/ void postBluetoothActiveDevice(BtDeviceInfo info, int delay) { sendLMsg(MSG_L_SET_BT_ACTIVE_DEVICE, SENDMSG_QUEUE, info, delay); } @@ -1803,6 +1805,7 @@ public class AudioDeviceBroker { onSetForceUse(msg.arg1, msg.arg2, false, (String) msg.obj); break; case MSG_L_SET_FORCE_BT_A2DP_USE: + case MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE: int forcedUsage = mBluetoothA2dpEnabled.get() ? AudioSystem.FORCE_NONE : AudioSystem.FORCE_NO_BT_A2DP; onSetForceUse(AudioSystem.FOR_MEDIA, forcedUsage, true, (String) msg.obj); @@ -2139,8 +2142,7 @@ public class AudioDeviceBroker { private static final int MSG_I_UPDATE_LE_AUDIO_GROUP_ADDRESSES = 57; private static final int MSG_L_SYNCHRONIZE_ADI_DEVICES_IN_INVENTORY = 58; private static final int MSG_IL_UPDATED_ADI_DEVICE_STATE = 59; - - + private static final int MSG_L_SET_FORCE_BT_A2DP_USE_NO_MUTE = 60; private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index add84910bf48..7deef2ffb5dd 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; } } @@ -7896,7 +7897,7 @@ public class AudioService extends IAudioService.Stub + previousDevice + " -> " + newDevice + ". Got: " + profile); } - sDeviceLogger.enqueue(new EventLogger.StringEvent("BlutoothActiveDeviceChanged for " + sDeviceLogger.enqueue(new EventLogger.StringEvent("BluetoothActiveDeviceChanged for " + BluetoothProfile.getProfileName(profile) + ", device update " + previousDevice + " -> " + newDevice).printLog(TAG)); AudioDeviceBroker.BtDeviceChangedData data = @@ -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/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index a649d34884a7..f8c4116e1e5a 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -289,6 +289,7 @@ public class BtHelper { Log.e(TAG, "Exception while getting status of " + device, e); } if (btCodecStatus == null) { + Log.e(TAG, "getCodec, null A2DP codec status for device: " + device); mA2dpCodecConfig = null; return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); } @@ -316,6 +317,7 @@ public class BtHelper { Log.e(TAG, "Exception while getting status of " + device, e); } if (btLeCodecStatus == null) { + Log.e(TAG, "getCodec, null LE codec status for device: " + device); mLeAudioCodecConfig = null; return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); } @@ -363,6 +365,7 @@ public class BtHelper { return new Pair<>(profile == BluetoothProfile.A2DP ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3, true); } + return codecAndChanged; } @@ -653,7 +656,7 @@ public class BtHelper { // Not a valid profile to connect Log.e(TAG, "onBtProfileConnected: Not a valid profile to connect " + BluetoothProfile.getProfileName(profile)); - break; + return; } // this part is only for A2DP, LE Audio unicast and Hearing aid @@ -664,19 +667,67 @@ public class BtHelper { return; } List<BluetoothDevice> activeDevices = adapter.getActiveDevices(profile); - BluetoothProfileConnectionInfo bpci = new BluetoothProfileConnectionInfo(profile); - for (BluetoothDevice device : activeDevices) { - if (device == null) { - continue; - } - AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData( - device, null, bpci, "mBluetoothProfileServiceListener"); - AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo( - data, device, BluetoothProfile.STATE_CONNECTED); - mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */); + if (activeDevices.isEmpty() || activeDevices.get(0) == null) { + return; + } + BluetoothDevice device = activeDevices.get(0); + switch (profile) { + case BluetoothProfile.A2DP: { + BluetoothProfileConnectionInfo bpci = + BluetoothProfileConnectionInfo.createA2dpInfo(false, -1); + postBluetoothActiveDevice(device, bpci); + } break; + case BluetoothProfile.HEARING_AID: { + BluetoothProfileConnectionInfo bpci = + BluetoothProfileConnectionInfo.createHearingAidInfo(false); + postBluetoothActiveDevice(device, bpci); + } break; + case BluetoothProfile.LE_AUDIO: { + int groupId = mLeAudio.getGroupId(device); + BluetoothLeAudioCodecStatus btLeCodecStatus = null; + try { + btLeCodecStatus = mLeAudio.getCodecStatus(groupId); + } catch (Exception e) { + Log.e(TAG, "Exception while getting status of " + device, e); + } + if (btLeCodecStatus == null) { + Log.i(TAG, "onBtProfileConnected null LE codec status for groupId: " + + groupId + ", device: " + device); + break; + } + List<BluetoothLeAudioCodecConfig> outputCodecConfigs = + btLeCodecStatus.getOutputCodecSelectableCapabilities(); + if (!outputCodecConfigs.isEmpty()) { + BluetoothProfileConnectionInfo bpci = + BluetoothProfileConnectionInfo.createLeAudioInfo( + false /*suppressNoisyIntent*/, true /*isLeOutput*/); + postBluetoothActiveDevice(device, bpci); + } + List<BluetoothLeAudioCodecConfig> inputCodecConfigs = + btLeCodecStatus.getInputCodecSelectableCapabilities(); + if (!inputCodecConfigs.isEmpty()) { + BluetoothProfileConnectionInfo bpci = + BluetoothProfileConnectionInfo.createLeAudioInfo( + false /*suppressNoisyIntent*/, false /*isLeOutput*/); + postBluetoothActiveDevice(device, bpci); + } + } break; + default: + // Not a valid profile to connect + Log.wtf(TAG, "Invalid profile! onBtProfileConnected"); + break; } } + private void postBluetoothActiveDevice( + BluetoothDevice device, BluetoothProfileConnectionInfo bpci) { + AudioDeviceBroker.BtDeviceChangedData data = new AudioDeviceBroker.BtDeviceChangedData( + device, null, bpci, "mBluetoothProfileServiceListener"); + AudioDeviceBroker.BtDeviceInfo info = mDeviceBroker.createBtDeviceInfo( + data, device, BluetoothProfile.STATE_CONNECTED); + mDeviceBroker.postBluetoothActiveDevice(info, 0 /* delay */); + } + /*package*/ synchronized boolean isProfilePoxyConnected(int profile) { switch (profile) { case BluetoothProfile.HEADSET: @@ -1198,21 +1249,46 @@ public class BtHelper { return AUDIO_DEVICE_CATEGORY_UNKNOWN; } String deviceCategory = new String(deviceType); - switch (deviceCategory) { - case DEVICE_TYPE_HEARING_AID: - return AUDIO_DEVICE_CATEGORY_HEARING_AID; - case DEVICE_TYPE_CARKIT: - return AUDIO_DEVICE_CATEGORY_CARKIT; - case DEVICE_TYPE_HEADSET: - case DEVICE_TYPE_UNTETHERED_HEADSET: - return AUDIO_DEVICE_CATEGORY_HEADPHONES; - case DEVICE_TYPE_SPEAKER: - return AUDIO_DEVICE_CATEGORY_SPEAKER; - case DEVICE_TYPE_WATCH: - return AUDIO_DEVICE_CATEGORY_WATCH; - case DEVICE_TYPE_DEFAULT: - default: - // fall through + + if (com.android.bluetooth.flags.Flags.supportMetadataDeviceTypesApis()) { + switch (deviceCategory) { + case DEVICE_TYPE_HEARING_AID: + return AUDIO_DEVICE_CATEGORY_HEARING_AID; + case DEVICE_TYPE_CARKIT: + return AUDIO_DEVICE_CATEGORY_CARKIT; + case DEVICE_TYPE_HEADSET: + case DEVICE_TYPE_UNTETHERED_HEADSET: + return AUDIO_DEVICE_CATEGORY_HEADPHONES; + case DEVICE_TYPE_SPEAKER: + return AUDIO_DEVICE_CATEGORY_SPEAKER; + case DEVICE_TYPE_WATCH: + return AUDIO_DEVICE_CATEGORY_WATCH; + case DEVICE_TYPE_DEFAULT: + // fall through + default: + break; + } + } else { + // Duplicate switch for now to cover the cases when the flag is not rolled out + // This will cover the cases in which clients could write directly to these + // metadata keys + switch (deviceCategory) { + case "HearingAid": + return AUDIO_DEVICE_CATEGORY_HEARING_AID; + case "Carkit": + return AUDIO_DEVICE_CATEGORY_CARKIT; + case "Headset": + case DEVICE_TYPE_UNTETHERED_HEADSET: + return AUDIO_DEVICE_CATEGORY_HEADPHONES; + case "Speaker": + return AUDIO_DEVICE_CATEGORY_SPEAKER; + case "Watch": + return AUDIO_DEVICE_CATEGORY_WATCH; + case "Default": + // fall through + default: + break; + } } BluetoothClass deviceClass = device.getBluetoothClass(); 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 new file mode 100644 index 000000000000..7cf2d3028aef --- /dev/null +++ b/services/core/java/com/android/server/biometrics/BiometricDanglingReceiver.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.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; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.biometrics.BiometricsProtoEnums; +import android.provider.Settings; +import android.util.Slog; + +import com.android.server.biometrics.sensors.BiometricNotificationUtils; + +/** + * Receives broadcast to biometrics dangling notification. + */ +public class BiometricDanglingReceiver extends BroadcastReceiver { + private static final String TAG = "BiometricDanglingReceiver"; + + public static final String ACTION_FINGERPRINT_RE_ENROLL_LAUNCH = + "action_fingerprint_re_enroll_launch"; + public static final String ACTION_FINGERPRINT_RE_ENROLL_DISMISS = + "action_fingerprint_re_enroll_dismiss"; + + public static final String ACTION_FACE_RE_ENROLL_LAUNCH = + "action_face_re_enroll_launch"; + public static final String ACTION_FACE_RE_ENROLL_DISMISS = + "action_face_re_enroll_dismiss"; + + public static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS"; + + private static final String SETTINGS_PACKAGE = "com.android.settings"; + + /** + * Constructor for BiometricDanglingReceiver. + * + * @param context context + * @param modality the value from BiometricsProtoEnums.MODALITY_* + */ + public BiometricDanglingReceiver(@NonNull Context context, int modality) { + final IntentFilter intentFilter = new IntentFilter(); + if (modality == BiometricsProtoEnums.MODALITY_FINGERPRINT) { + intentFilter.addAction(ACTION_FINGERPRINT_RE_ENROLL_LAUNCH); + intentFilter.addAction(ACTION_FINGERPRINT_RE_ENROLL_DISMISS); + } else if (modality == BiometricsProtoEnums.MODALITY_FACE) { + intentFilter.addAction(ACTION_FACE_RE_ENROLL_LAUNCH); + intentFilter.addAction(ACTION_FACE_RE_ENROLL_DISMISS); + } + context.registerReceiver(this, intentFilter, Context.RECEIVER_NOT_EXPORTED); + } + + @Override + public void onReceive(Context context, Intent intent) { + Slog.d(TAG, "Received: " + intent.getAction()); + if (ACTION_FINGERPRINT_RE_ENROLL_LAUNCH.equals(intent.getAction())) { + launchBiometricEnrollActivity(context, Settings.ACTION_FINGERPRINT_ENROLL); + BiometricNotificationUtils.cancelFingerprintReEnrollNotification(context); + } else if (ACTION_FINGERPRINT_RE_ENROLL_DISMISS.equals(intent.getAction())) { + BiometricNotificationUtils.cancelFingerprintReEnrollNotification(context); + } else if (ACTION_FACE_RE_ENROLL_LAUNCH.equals(intent.getAction())) { + launchBiometricEnrollActivity(context, FACE_SETTINGS_ACTION); + BiometricNotificationUtils.cancelFaceReEnrollNotification(context); + } else if (ACTION_FACE_RE_ENROLL_DISMISS.equals(intent.getAction())) { + BiometricNotificationUtils.cancelFaceReEnrollNotification(context); + } + context.unregisterReceiver(this); + } + + private void launchBiometricEnrollActivity(Context context, String action) { + 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); + context.startActivity(intent); + } +} diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java index 0e22f7511af9..53e6bdb2ab5f 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricNotificationUtils.java @@ -24,13 +24,18 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.face.FaceEnrollOptions; import android.hardware.fingerprint.FingerprintEnrollOptions; import android.os.SystemClock; import android.os.UserHandle; +import android.text.BidiFormatter; import android.util.Slog; import com.android.internal.R; +import com.android.server.biometrics.BiometricDanglingReceiver; + +import java.util.List; /** * Biometric notification helper class. @@ -39,6 +44,7 @@ public class BiometricNotificationUtils { private static final String TAG = "BiometricNotificationUtils"; private static final String FACE_RE_ENROLL_NOTIFICATION_TAG = "FaceReEnroll"; + private static final String FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG = "FingerprintReEnroll"; private static final String BAD_CALIBRATION_NOTIFICATION_TAG = "FingerprintBadCalibration"; private static final String KEY_RE_ENROLL_FACE = "re_enroll_face_unlock"; private static final String FACE_SETTINGS_ACTION = "android.settings.FACE_SETTINGS"; @@ -50,6 +56,8 @@ public class BiometricNotificationUtils { private static final String FACE_ENROLL_CHANNEL = "FaceEnrollNotificationChannel"; private static final String FACE_RE_ENROLL_CHANNEL = "FaceReEnrollNotificationChannel"; private static final String FINGERPRINT_ENROLL_CHANNEL = "FingerprintEnrollNotificationChannel"; + private static final String FINGERPRINT_RE_ENROLL_CHANNEL = + "FingerprintReEnrollNotificationChannel"; private static final String FINGERPRINT_BAD_CALIBRATION_CHANNEL = "FingerprintBadCalibrationNotificationChannel"; private static final long NOTIFICATION_INTERVAL_MS = 24 * 60 * 60 * 1000; @@ -177,10 +185,124 @@ public class BiometricNotificationUtils { BAD_CALIBRATION_NOTIFICATION_TAG, Notification.VISIBILITY_SECRET, false); } + /** + * Shows a biometric re-enroll notification. + */ + public static void showBiometricReEnrollNotification(@NonNull Context context, + @NonNull List<String> identifiers, boolean allIdentifiersDeleted, int modality) { + final boolean isFingerprint = modality == BiometricsProtoEnums.MODALITY_FINGERPRINT; + final String reEnrollName = isFingerprint ? FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG + : FACE_RE_ENROLL_NOTIFICATION_TAG; + if (identifiers.isEmpty()) { + Slog.v(TAG, "Skipping " + reEnrollName + " notification : empty list"); + return; + } + Slog.d(TAG, "Showing " + reEnrollName + " notification :[" + identifiers.size() + + " identifier(s) deleted, allIdentifiersDeleted=" + allIdentifiersDeleted + "]"); + + final String name = + context.getString(R.string.device_unlock_notification_name); + final String title = context.getString(isFingerprint + ? R.string.fingerprint_dangling_notification_title + : R.string.face_dangling_notification_title); + final String content = isFingerprint + ? getFingerprintDanglingContentString(context, identifiers, allIdentifiersDeleted) + : context.getString(R.string.face_dangling_notification_msg); + + // Create "Set up" notification action button. + final Intent setupIntent = new Intent( + isFingerprint ? BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_LAUNCH + : BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_LAUNCH); + final PendingIntent setupPendingIntent = PendingIntent.getBroadcastAsUser(context, 0, + setupIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT); + final String setupText = + context.getString(R.string.biometric_dangling_notification_action_set_up); + final Notification.Action setupAction = new Notification.Action.Builder( + null, setupText, setupPendingIntent).build(); + + // Create "Not now" notification action button. + final Intent notNowIntent = new Intent( + isFingerprint ? BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_DISMISS + : BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_DISMISS); + final PendingIntent notNowPendingIntent = PendingIntent.getBroadcastAsUser(context, 0, + notNowIntent, PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT); + final String notNowText = context.getString( + R.string.biometric_dangling_notification_action_not_now); + final Notification.Action notNowAction = new Notification.Action.Builder( + null, notNowText, notNowPendingIntent).build(); + + final String channel = isFingerprint ? FINGERPRINT_RE_ENROLL_CHANNEL + : FACE_RE_ENROLL_CHANNEL; + final String tag = isFingerprint ? FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG + : FACE_RE_ENROLL_NOTIFICATION_TAG; + + showNotificationHelper(context, name, title, content, setupPendingIntent, setupAction, + notNowAction, Notification.CATEGORY_SYSTEM, channel, tag, + Notification.VISIBILITY_SECRET, false, Notification.FLAG_NO_CLEAR); + } + + private static String getFingerprintDanglingContentString(Context context, + @NonNull List<String> fingerprints, boolean allFingerprintDeleted) { + if (fingerprints.isEmpty()) { + return null; + } + + final int resId; + final int size = fingerprints.size(); + final StringBuilder first = new StringBuilder(); + final BidiFormatter bidiFormatter = BidiFormatter.getInstance(); + if (size > 1) { + // If there are more than 1 fingerprint deleted, the "second" will be the last + // fingerprint and set the others to "first". + // For example, if we have 3 fingerprints deleted(fp1, fp2 and fp3): + // first = "fp1, fp2" + // second = "fp3" + final String separator = ", "; + String second = null; + for (int i = 0; i < size; i++) { + if (i == size - 1) { + second = bidiFormatter.unicodeWrap("\"" + fingerprints.get(i) + "\""); + } else { + first.append(bidiFormatter.unicodeWrap("\"")); + first.append(bidiFormatter.unicodeWrap(fingerprints.get(i))); + first.append(bidiFormatter.unicodeWrap("\"")); + if (i < size - 2) { + first.append(bidiFormatter.unicodeWrap(separator)); + } + } + } + if (allFingerprintDeleted) { + resId = R.string.fingerprint_dangling_notification_msg_all_deleted_2; + } else { + resId = R.string.fingerprint_dangling_notification_msg_2; + } + + return String.format(context.getString(resId), first, second); + } else { + if (allFingerprintDeleted) { + resId = R.string.fingerprint_dangling_notification_msg_all_deleted_1; + } else { + resId = R.string.fingerprint_dangling_notification_msg_1; + } + first.append(bidiFormatter.unicodeWrap("\"")); + first.append(bidiFormatter.unicodeWrap(fingerprints.get(0))); + first.append(bidiFormatter.unicodeWrap("\"")); + return String.format(context.getString(resId), first); + } + } + private static void showNotificationHelper(Context context, String name, String title, - String content, PendingIntent pendingIntent, String category, - String channelName, String notificationTag, int visibility, - boolean listenToDismissEvent) { + String content, PendingIntent pendingIntent, String category, String channelName, + String notificationTag, int visibility, boolean listenToDismissEvent) { + showNotificationHelper(context, name, title, content, pendingIntent, + null /* positiveAction */, null /* negativeAction */, category, channelName, + notificationTag, visibility, listenToDismissEvent, 0); + } + + private static void showNotificationHelper(Context context, String name, String title, + String content, PendingIntent pendingIntent, Notification.Action positiveAction, + Notification.Action negativeAction, String category, String channelName, + String notificationTag, int visibility, boolean listenToDismissEvent, int flags) { Slog.v(TAG," listenToDismissEvent = " + listenToDismissEvent); final PendingIntent dismissIntent = PendingIntent.getActivityAsUser(context, 0 /* requestCode */, DISMISS_FRR_INTENT, PendingIntent.FLAG_IMMUTABLE /* flags */, @@ -202,6 +324,15 @@ public class BiometricNotificationUtils { .setContentIntent(pendingIntent) .setVisibility(visibility); + if (flags > 0) { + builder.setFlag(flags, true); + } + if (positiveAction != null) { + builder.addAction(positiveAction); + } + if (negativeAction != null) { + builder.addAction(negativeAction); + } if (listenToDismissEvent) { builder.setDeleteIntent(dismissIntent); } @@ -253,4 +384,14 @@ public class BiometricNotificationUtils { UserHandle.CURRENT); } + /** + * Cancels a fingerprint enrollment notification + */ + public static void cancelFingerprintReEnrollNotification(@NonNull Context context) { + final NotificationManager notificationManager = + context.getSystemService(NotificationManager.class); + notificationManager.cancelAsUser(FINGERPRINT_RE_ENROLL_NOTIFICATION_TAG, NOTIFICATION_ID, + UserHandle.CURRENT); + } + } diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java index 6daaad1baf83..81ab26dd581e 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java @@ -22,6 +22,7 @@ import android.hardware.biometrics.BiometricAuthenticator; import android.os.IBinder; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.BiometricsProto; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; @@ -44,6 +45,7 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> private List<? extends BiometricAuthenticator.Identifier> mEnrolledList; // List of templates to remove from the HAL private List<BiometricAuthenticator.Identifier> mUnknownHALTemplates = new ArrayList<>(); + private final int mInitialEnrolledSize; protected InternalEnumerateClient(@NonNull Context context, @NonNull Supplier<T> lazyDaemon, @NonNull IBinder token, int userId, @NonNull String owner, @@ -55,6 +57,7 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> super(context, lazyDaemon, token, null /* ClientMonitorCallbackConverter */, userId, owner, 0 /* cookie */, sensorId, logger, biometricContext); mEnrolledList = enrolledList; + mInitialEnrolledSize = mEnrolledList.size(); mUtils = utils; } @@ -111,8 +114,10 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> // At this point, mEnrolledList only contains templates known to the framework and // not the HAL. + final List<String> names = new ArrayList<>(); for (int i = 0; i < mEnrolledList.size(); i++) { BiometricAuthenticator.Identifier identifier = mEnrolledList.get(i); + names.add(identifier.getName().toString()); Slog.e(TAG, "doTemplateCleanup(): Removing dangling template from framework: " + identifier.getBiometricId() + " " + identifier.getName()); mUtils.removeBiometricForUser(getContext(), @@ -120,6 +125,11 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> getLogger().logUnknownEnrollmentInFramework(); } + + // Send dangling notification. + if (!names.isEmpty()) { + sendDanglingNotification(names); + } mEnrolledList.clear(); } @@ -127,8 +137,24 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> return mUnknownHALTemplates; } + /** + * Send the dangling notification. + */ + @VisibleForTesting + public void sendDanglingNotification(@NonNull List<String> identifierNames) { + if (!identifierNames.isEmpty()) { + Slog.e(TAG, "sendDanglingNotification(): initial enrolledSize=" + + mInitialEnrolledSize + ", after clean up size=" + mEnrolledList.size()); + final boolean allIdentifiersDeleted = mEnrolledList.size() == mInitialEnrolledSize; + BiometricNotificationUtils.showBiometricReEnrollNotification( + getContext(), identifierNames, allIdentifiersDeleted, getModality()); + } + } + @Override public int getProtoEnum() { return BiometricsProto.CM_ENUMERATE; } + + protected abstract int getModality(); } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java index d85455e0e76b..6ce3bc59eb56 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClient.java @@ -18,12 +18,14 @@ package com.android.server.biometrics.sensors.face.aidl; import android.annotation.NonNull; import android.content.Context; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.face.IFace; import android.hardware.face.Face; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; @@ -35,7 +37,8 @@ import java.util.function.Supplier; /** * Face-specific internal enumerate client for the {@link IFace} AIDL HAL interface. */ -class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> { +@VisibleForTesting +public class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> { private static final String TAG = "FaceInternalEnumerateClient"; FaceInternalEnumerateClient(@NonNull Context context, @@ -56,4 +59,9 @@ class FaceInternalEnumerateClient extends InternalEnumerateClient<AidlSession> { mCallback.onClientFinished(this, false /* success */); } } + + @Override + protected int getModality() { + return BiometricsProtoEnums.MODALITY_FACE; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index e71cffe0b8c5..f0a418951505 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -52,6 +52,7 @@ import android.view.Surface; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.BiometricDanglingReceiver; import com.android.server.biometrics.BiometricHandlerProvider; import com.android.server.biometrics.Utils; import com.android.server.biometrics.log.BiometricContext; @@ -201,6 +202,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { mBiometricHandlerProvider = biometricHandlerProvider; initAuthenticationBroadcastReceiver(); + initFaceDanglingBroadcastReceiver(); initSensors(resetLockoutRequiresChallenge, props); } @@ -214,6 +216,10 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { }); } + private void initFaceDanglingBroadcastReceiver() { + new BiometricDanglingReceiver(mContext, BiometricsProtoEnums.MODALITY_FACE); + } + private void initSensors(boolean resetLockoutRequiresChallenge, SensorProps[] props) { if (resetLockoutRequiresChallenge) { Slog.d(getTag(), "Adding HIDL configs"); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java index d64b6c29c840..8dc560b0e0b5 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java @@ -20,6 +20,9 @@ import android.annotation.NonNull; import android.content.Context; import android.hardware.biometrics.ITestSession; import android.hardware.biometrics.ITestSessionCallback; +import android.hardware.biometrics.fingerprint.AcquiredInfoAndVendorCode; +import android.hardware.biometrics.fingerprint.EnrollmentProgressStep; +import android.hardware.biometrics.fingerprint.NextEnrollment; import android.hardware.fingerprint.Fingerprint; import android.hardware.fingerprint.FingerprintEnrollOptions; import android.hardware.fingerprint.FingerprintManager; @@ -46,6 +49,7 @@ import java.util.Set; class BiometricTestSessionImpl extends ITestSession.Stub { private static final String TAG = "fp/aidl/BiometricTestSessionImpl"; + private static final int VHAL_ENROLLMENT_ID = 9999; @NonNull private final Context mContext; private final int mSensorId; @@ -140,8 +144,8 @@ class BiometricTestSessionImpl extends ITestSession.Stub { super.setTestHalEnabled_enforcePermission(); - mProvider.setTestHalEnabled(enabled); mSensor.setTestHalEnabled(enabled); + mProvider.setTestHalEnabled(enabled); } @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) @@ -157,10 +161,31 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) @Override - public void finishEnroll(int userId) { + public void finishEnroll(int userId) throws RemoteException { super.finishEnroll_enforcePermission(); + Slog.i(TAG, "finishEnroll(): useVhalForTesting=" + mProvider.useVhalForTesting()); + if (mProvider.useVhalForTesting()) { + final AcquiredInfoAndVendorCode[] acquiredInfoAndVendorCodes = + {new AcquiredInfoAndVendorCode()}; + final EnrollmentProgressStep[] enrollmentProgressSteps = + {new EnrollmentProgressStep(), new EnrollmentProgressStep()}; + enrollmentProgressSteps[0].durationMs = 100; + enrollmentProgressSteps[0].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes; + enrollmentProgressSteps[1].durationMs = 200; + enrollmentProgressSteps[1].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes; + + final NextEnrollment nextEnrollment = new NextEnrollment(); + nextEnrollment.id = VHAL_ENROLLMENT_ID; + nextEnrollment.progressSteps = enrollmentProgressSteps; + nextEnrollment.result = true; + mProvider.getVhal().setNextEnrollment(nextEnrollment); + mProvider.simulateVhalFingerDown(userId, mSensorId); + return; + } + + //TODO (b341889971): delete the following lines when b/341889971 is resolved int nextRandomId = mRandom.nextInt(); while (mEnrollmentIds.contains(nextRandomId)) { nextRandomId = mRandom.nextInt(); @@ -173,11 +198,18 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) @Override - public void acceptAuthentication(int userId) { + public void acceptAuthentication(int userId) throws RemoteException { // Fake authentication with any of the existing fingers super.acceptAuthentication_enforcePermission(); + if (mProvider.useVhalForTesting()) { + mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID); + mProvider.simulateVhalFingerDown(userId, mSensorId); + return; + } + + //TODO (b341889971): delete the following lines when b/341889971 is resolved List<Fingerprint> fingerprints = FingerprintUtils.getInstance(mSensorId) .getBiometricsForUser(mContext, userId); if (fingerprints.isEmpty()) { @@ -191,10 +223,17 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) @Override - public void rejectAuthentication(int userId) { + public void rejectAuthentication(int userId) throws RemoteException { super.rejectAuthentication_enforcePermission(); + if (mProvider.useVhalForTesting()) { + mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID + 1); + mProvider.simulateVhalFingerDown(userId, mSensorId); + return; + } + + //TODO (b341889971): delete the following lines when b/341889971 is resolved mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed(); } @@ -220,11 +259,17 @@ class BiometricTestSessionImpl extends ITestSession.Stub { @android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC) @Override - public void cleanupInternalState(int userId) { + public void cleanupInternalState(int userId) throws RemoteException { super.cleanupInternalState_enforcePermission(); Slog.d(TAG, "cleanupInternalState: " + userId); + + if (mProvider.useVhalForTesting()) { + Slog.i(TAG, "cleanup virtualhal configurations"); + mProvider.getVhal().resetConfigurations(); //setEnrollments(new int[]{}); + } + mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() { @Override public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) { @@ -248,4 +293,4 @@ class BiometricTestSessionImpl extends ITestSession.Stub { } }); } -} +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java index a5a832aaaf59..2849bd9c92a3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClient.java @@ -18,11 +18,13 @@ package com.android.server.biometrics.sensors.fingerprint.aidl; import android.annotation.NonNull; import android.content.Context; +import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.fingerprint.Fingerprint; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; import com.android.server.biometrics.sensors.BiometricUtils; @@ -35,7 +37,8 @@ import java.util.function.Supplier; * Fingerprint-specific internal client supporting the * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface. */ -class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSession> { +@VisibleForTesting +public class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSession> { private static final String TAG = "FingerprintInternalEnumerateClient"; protected FingerprintInternalEnumerateClient(@NonNull Context context, @@ -56,4 +59,9 @@ class FingerprintInternalEnumerateClient extends InternalEnumerateClient<AidlSes mCallback.onClientFinished(this, false /* success */); } } + + @Override + protected int getModality() { + return BiometricsProtoEnums.MODALITY_FINGERPRINT; + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 6874c71267fb..1bddb83b1441 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -58,6 +58,7 @@ import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver; import com.android.server.biometrics.AuthenticationStatsCollector; +import com.android.server.biometrics.BiometricDanglingReceiver; import com.android.server.biometrics.BiometricHandlerProvider; import com.android.server.biometrics.Flags; import com.android.server.biometrics.Utils; @@ -205,6 +206,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi mBiometricHandlerProvider = biometricHandlerProvider; initAuthenticationBroadcastReceiver(); + initFingerprintDanglingBroadcastReceiver(); initSensors(resetLockoutRequiresHardwareAuthToken, props, gestureAvailabilityDispatcher); } @@ -218,6 +220,10 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi }); } + private void initFingerprintDanglingBroadcastReceiver() { + new BiometricDanglingReceiver(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT); + } + private void initSensors(boolean resetLockoutRequiresHardwareAuthToken, SensorProps[] props, GestureAvailabilityDispatcher gestureAvailabilityDispatcher) { if (!resetLockoutRequiresHardwareAuthToken) { @@ -884,7 +890,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } void setTestHalEnabled(boolean enabled) { + final boolean changed = enabled != mTestHalEnabled; mTestHalEnabled = enabled; + Slog.i(getTag(), "setTestHalEnabled(): useVhalForTesting=" + Flags.useVhalForTesting() + + "mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed); + if (changed && useVhalForTesting()) { + getHalInstance(); + } } public boolean getTestHalEnabled() { @@ -976,7 +988,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi if (mVhal == null && useVhalForTesting()) { mVhal = IVirtualHal.Stub.asInterface(mDaemon.asBinder().getExtension()); if (mVhal == null) { - Slog.e(getTag(), "Unable to get virtual hal interface"); + Slog.e(getTag(), "Unable to get fingerprint virtualhal interface"); } } 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/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 9c020a7d4fda..30d12e670a6c 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -181,7 +181,7 @@ public class AutomaticBrightnessController { private long mLightSensorEnableTime; // The currently accepted nominal ambient light level. - private float mAmbientLux; + private float mAmbientLux = INVALID_LUX; // The last calculated ambient light level (long time window). private float mSlowAmbientLux; @@ -434,23 +434,23 @@ public class AutomaticBrightnessController { * entering doze - we disable the light sensor, invalidate the lux, but we still need to set * the initial brightness in doze mode. */ - public float getAutomaticScreenBrightnessBasedOnLastObservedLux( + public float getAutomaticScreenBrightnessBasedOnLastUsedLux( BrightnessEvent brightnessEvent) { - if (mLastObservedLux == INVALID_LUX) { + float lastUsedLux = mAmbientLux; + if (lastUsedLux == INVALID_LUX) { return PowerManager.BRIGHTNESS_INVALID_FLOAT; } - float brightness = mCurrentBrightnessMapper.getBrightness(mLastObservedLux, + float brightness = mCurrentBrightnessMapper.getBrightness(lastUsedLux, mForegroundAppPackageName, mForegroundAppCategory); if (shouldApplyDozeScaleFactor()) { brightness *= mDozeScaleFactor; } if (brightnessEvent != null) { - brightnessEvent.setLux(mLastObservedLux); + brightnessEvent.setLux(lastUsedLux); brightnessEvent.setRecommendedBrightness(brightness); brightnessEvent.setFlags(brightnessEvent.getFlags() - | (mLastObservedLux == INVALID_LUX ? BrightnessEvent.FLAG_INVALID_LUX : 0) | (shouldApplyDozeScaleFactor() ? BrightnessEvent.FLAG_DOZE_SCALE : 0)); brightnessEvent.setAutoBrightnessMode(getMode()); } @@ -1189,7 +1189,11 @@ public class AutomaticBrightnessController { update(); } - void switchMode(@AutomaticBrightnessMode int mode) { + /** + * Responsible for switching the AutomaticBrightnessMode of the associated display. Also takes + * care of resetting the short term model wherever required + */ + public void switchMode(@AutomaticBrightnessMode int mode) { if (!mBrightnessMappingStrategyMap.contains(mode)) { return; } diff --git a/services/core/java/com/android/server/display/DisplayControl.java b/services/core/java/com/android/server/display/DisplayControl.java index 22f3bbd80a77..38eb416ffdd8 100644 --- a/services/core/java/com/android/server/display/DisplayControl.java +++ b/services/core/java/com/android/server/display/DisplayControl.java @@ -28,9 +28,9 @@ import java.util.Objects; * Calls into SurfaceFlinger for Display creation and deletion. */ public class DisplayControl { - private static native IBinder nativeCreateDisplay(String name, boolean secure, - float requestedRefreshRate); - private static native void nativeDestroyDisplay(IBinder displayToken); + private static native IBinder nativeCreateVirtualDisplay(String name, boolean secure, + String uniqueId, float requestedRefreshRate); + private static native void nativeDestroyVirtualDisplay(IBinder displayToken); private static native void nativeOverrideHdrTypes(IBinder displayToken, int[] modes); private static native long[] nativeGetPhysicalDisplayIds(); private static native IBinder nativeGetPhysicalDisplayToken(long physicalDisplayId); @@ -41,22 +41,23 @@ public class DisplayControl { private static native boolean nativeGetHdrOutputConversionSupport(); /** - * Create a display in SurfaceFlinger. + * Create a virtual display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the virtual 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) { + public static IBinder createVirtualDisplay(String name, boolean secure) { Objects.requireNonNull(name, "name must not be null"); - return nativeCreateDisplay(name, secure, 0.0f); + return nativeCreateVirtualDisplay(name, secure, "", 0.0f); } /** - * Create a display in SurfaceFlinger. + * Create a virtual display in SurfaceFlinger. * - * @param name The name of the display + * @param name The name of the virtual 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 @@ -64,23 +65,23 @@ public class DisplayControl { * display is refreshed at the physical display refresh rate. * @return The token reference for the display in SurfaceFlinger. */ - public static IBinder createDisplay(String name, boolean secure, - float requestedRefreshRate) { + public static IBinder createVirtualDisplay(String name, boolean secure, + 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 nativeCreateVirtualDisplay(name, secure, uniqueId, requestedRefreshRate); } /** - * Destroy a display in SurfaceFlinger. + * Destroy a virtual display in SurfaceFlinger. * - * @param displayToken The display token for the display to be destroyed. + * @param displayToken The display token for the virtual display to be destroyed. */ - public static void destroyDisplay(IBinder displayToken) { + public static void destroyVirtualDisplay(IBinder displayToken) { if (displayToken == null) { throw new IllegalArgumentException("displayToken must not be null"); } - - nativeDestroyDisplay(displayToken); + nativeDestroyVirtualDisplay(displayToken); } /** diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index a6335e3ebf70..eeacc53f0fd4 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -1268,7 +1268,7 @@ public class DisplayDeviceConfig { return mAmbientLightSensor; } - SensorData getScreenOffBrightnessSensor() { + public SensorData getScreenOffBrightnessSensor() { return mScreenOffBrightnessSensor; } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index c5d8686e3a52..0fcdf198eece 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -84,6 +84,7 @@ import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.brightness.DisplayBrightnessController; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2; +import com.android.server.display.brightness.strategy.DisplayBrightnessStrategyConstants; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; import com.android.server.display.config.HysteresisLevels; @@ -797,7 +798,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call return; } mDisplayStateController.overrideDozeScreenState(displayState, reason); - sendUpdatePowerState(); + updatePowerState(); }, mClock.uptimeMillis()); } @@ -851,6 +852,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mHandler.postAtTime(() -> { boolean changed = false; + + if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { + changed = true; + mIsEnabled = isEnabled; + mIsInTransition = isInTransition; + } + if (mDisplayDevice != device) { changed = true; mDisplayDevice = device; @@ -875,11 +883,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mThermalBrightnessThrottlingDataId, mUniqueDisplayId); } - if (mIsEnabled != isEnabled || mIsInTransition != isInTransition) { - changed = true; - mIsEnabled = isEnabled; - mIsInTransition = isInTransition; - } mIsDisplayInternal = isDisplayInternal; // using local variables here, when mBrightnessThrottler is removed, @@ -1113,29 +1116,29 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessThrottler, mDisplayDeviceConfig.getAmbientHorizonShort(), mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userNits, mBrightnessClamperController); - mDisplayBrightnessController.setAutomaticBrightnessController( - mAutomaticBrightnessController); - - mAutomaticBrightnessStrategy - .setAutomaticBrightnessController(mAutomaticBrightnessController); + mDisplayBrightnessController.setUpAutoBrightness( + mAutomaticBrightnessController, mSensorManager, mDisplayDeviceConfig, mHandler, + defaultModeBrightnessMapper, mIsEnabled, mLeadDisplayId); mBrightnessEventRingBuffer = new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX); - - if (mScreenOffBrightnessSensorController != null) { - mScreenOffBrightnessSensorController.stop(); - mScreenOffBrightnessSensorController = null; - } - loadScreenOffBrightnessSensor(); - int[] sensorValueToLux = mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux(); - if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) { - mScreenOffBrightnessSensorController = - mInjector.getScreenOffBrightnessSensorController( - mSensorManager, - mScreenOffBrightnessSensor, - mHandler, - SystemClock::uptimeMillis, - sensorValueToLux, - defaultModeBrightnessMapper); + if (!mFlags.isRefactorDisplayPowerControllerEnabled()) { + if (mScreenOffBrightnessSensorController != null) { + mScreenOffBrightnessSensorController.stop(); + mScreenOffBrightnessSensorController = null; + } + loadScreenOffBrightnessSensor(); + int[] sensorValueToLux = + mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux(); + if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) { + mScreenOffBrightnessSensorController = + mInjector.getScreenOffBrightnessSensorController( + mSensorManager, + mScreenOffBrightnessSensor, + mHandler, + SystemClock::uptimeMillis, + sensorValueToLux, + defaultModeBrightnessMapper); + } } } else { mUseSoftwareAutoBrightnessConfig = false; @@ -1255,7 +1258,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mPowerState = null; } - if (mScreenOffBrightnessSensorController != null) { + if (!mFlags.isRefactorDisplayPowerControllerEnabled() + && mScreenOffBrightnessSensorController != null) { mScreenOffBrightnessSensorController.stop(); } @@ -1330,12 +1334,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mDisplayStateController.shouldPerformScreenOffTransition()); state = mPowerState.getScreenState(); - // Switch to doze auto-brightness mode if needed - if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null - && !mAutomaticBrightnessController.isInIdleMode()) { - mAutomaticBrightnessController.switchMode(Display.isDozeState(state) - ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT); - } DisplayBrightnessState displayBrightnessState = mDisplayBrightnessController .updateBrightness(mPowerRequest, state); @@ -1351,15 +1349,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (displayBrightnessState.getBrightnessEvent() != null) { mTempBrightnessEvent.copyFrom(displayBrightnessState.getBrightnessEvent()); } - // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor - // doesn't yet have a valid lux value to use with auto-brightness. - if (mScreenOffBrightnessSensorController != null) { - mScreenOffBrightnessSensorController - .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness() - && mIsEnabled && (state == Display.STATE_OFF - || (state == Display.STATE_DOZE - && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig())) - && mLeadDisplayId == Layout.NO_LEAD_DISPLAY); + if (!mFlags.isRefactorDisplayPowerControllerEnabled()) { + // Set up the ScreenOff controller used when coming out of SCREEN_OFF and the ALS sensor + // doesn't yet have a valid lux value to use with auto-brightness. + if (mScreenOffBrightnessSensorController != null) { + mScreenOffBrightnessSensorController + .setLightSensorEnabled(displayBrightnessState.getShouldUseAutoBrightness() + && mIsEnabled && (state == Display.STATE_OFF + || (state == Display.STATE_DOZE + && !mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig())) + && mLeadDisplayId == Layout.NO_LEAD_DISPLAY); + } } // Take note if the short term model was already active before applying the current @@ -1367,6 +1367,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call final boolean wasShortTermModelActive = mAutomaticBrightnessStrategy.isShortTermModelActive(); if (!mFlags.isRefactorDisplayPowerControllerEnabled()) { + // Switch to doze auto-brightness mode if needed + if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null + && !mAutomaticBrightnessController.isInIdleMode()) { + mAutomaticBrightnessController.switchMode(Display.isDozeState(state) + ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT); + } + mAutomaticBrightnessStrategy.setAutoBrightnessState(state, mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(), mBrightnessReasonTemp.getReason(), mPowerRequest.policy, @@ -1392,15 +1399,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call boolean updateScreenBrightnessSetting = displayBrightnessState.shouldUpdateScreenBrightnessSetting(); float currentBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness(); - // Apply auto-brightness. - // All the conditions inside this if block will be moved to AutomaticBrightnessStrategy - if (mFlags.isRefactorDisplayPowerControllerEnabled() - && displayBrightnessState.getBrightnessReason().getReason() - == BrightnessReason.REASON_AUTOMATIC) { - if (mScreenOffBrightnessSensorController != null) { - mScreenOffBrightnessSensorController.setLightSensorEnabled(false); - } - } if (!mFlags.isRefactorDisplayPowerControllerEnabled()) { // AutomaticBrightnessStrategy has higher priority than OffloadBrightnessStrategy @@ -1444,53 +1442,76 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call brightnessState = clampScreenBrightness(brightnessState); } - // If there's an offload session, we need to set the initial doze brightness before - // the offload session starts controlling the brightness. - if (Float.isNaN(brightnessState) && mFlags.isDisplayOffloadEnabled() - && Display.isDozeState(state) && mDisplayOffloadSession != null) { - if (mAutomaticBrightnessController != null - && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { - // Use the auto-brightness curve and the last observed lux - rawBrightnessState = mAutomaticBrightnessController - .getAutomaticScreenBrightnessBasedOnLastObservedLux( - mTempBrightnessEvent); - } else { - rawBrightnessState = getDozeBrightnessForOffload(); - mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() - | BrightnessEvent.FLAG_DOZE_SCALE); + if (Display.isDozeState(state)) { + // 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) + || displayBrightnessState.getDisplayBrightnessStrategyName() + .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME)) + && mFlags.isDisplayOffloadEnabled() + && mDisplayOffloadSession != null) { + if (mAutomaticBrightnessController != null + && mAutomaticBrightnessStrategy.shouldUseAutoBrightness()) { + // Use the auto-brightness curve and the last observed lux + rawBrightnessState = mAutomaticBrightnessController + .getAutomaticScreenBrightnessBasedOnLastUsedLux( + mTempBrightnessEvent); + } else { + rawBrightnessState = getDozeBrightnessForOffload(); + mTempBrightnessEvent.setFlags(mTempBrightnessEvent.getFlags() + | BrightnessEvent.FLAG_DOZE_SCALE); + } + + 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; + } + } } - if (BrightnessUtils.isValidBrightnessValue(rawBrightnessState)) { + // Use default brightness when dozing unless overridden. + if (Float.isNaN(brightnessState) + || displayBrightnessState.getDisplayBrightnessStrategyName() + .equals(DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME)) { + rawBrightnessState = mScreenBrightnessDozeConfig; brightnessState = clampScreenBrightness(rawBrightnessState); - mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_INITIAL); + mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); } } - // Use default brightness when dozing unless overridden. - if (Float.isNaN(brightnessState) && Display.isDozeState(state)) { - rawBrightnessState = mScreenBrightnessDozeConfig; - brightnessState = clampScreenBrightness(rawBrightnessState); - mBrightnessReasonTemp.setReason(BrightnessReason.REASON_DOZE_DEFAULT); - } - - // The ALS is not available yet - use the screen off sensor to determine the initial - // brightness - if (Float.isNaN(brightnessState) && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled() - && mScreenOffBrightnessSensorController != null) { - rawBrightnessState = - mScreenOffBrightnessSensorController.getAutomaticScreenBrightness(); - brightnessState = rawBrightnessState; - if (BrightnessUtils.isValidBrightnessValue(brightnessState)) { - brightnessState = clampScreenBrightness(brightnessState); - updateScreenBrightnessSetting = mDisplayBrightnessController.getCurrentBrightness() - != brightnessState; - mBrightnessReasonTemp.setReason( - BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR); + if (!mFlags.isRefactorDisplayPowerControllerEnabled()) { + // The ALS is not available yet - use the screen off sensor to determine the initial + // brightness + if (Float.isNaN(brightnessState) + && mAutomaticBrightnessStrategy.isAutoBrightnessEnabled() + && mScreenOffBrightnessSensorController != null) { + rawBrightnessState = + mScreenOffBrightnessSensorController.getAutomaticScreenBrightness(); + brightnessState = rawBrightnessState; + if (BrightnessUtils.isValidBrightnessValue(brightnessState)) { + brightnessState = clampScreenBrightness(brightnessState); + updateScreenBrightnessSetting = + mDisplayBrightnessController.getCurrentBrightness() + != brightnessState; + mBrightnessReasonTemp.setReason( + BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR); + } } } // Apply manual brightness. - if (Float.isNaN(brightnessState)) { + if (Float.isNaN(brightnessState) && !mFlags.isRefactorDisplayPowerControllerEnabled()) { rawBrightnessState = currentBrightnessSetting; brightnessState = clampScreenBrightness(rawBrightnessState); if (brightnessState != currentBrightnessSetting) { diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java index 22ff2d0eeffd..eb76dcba3b85 100644 --- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java @@ -309,7 +309,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mSurface.release(); mSurface = null; } - DisplayControl.destroyDisplay(getDisplayTokenLocked()); + DisplayControl.destroyVirtualDisplay(getDisplayTokenLocked()); } @Override @@ -467,7 +467,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate, long presentationDeadlineNanos, int state) { synchronized (getSyncRoot()) { - IBinder displayToken = DisplayControl.createDisplay(mName, mFlags.mSecure); + IBinder displayToken = DisplayControl.createVirtualDisplay(mName, mFlags.mSecure); mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode, DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos, mFlags, state, surfaceTexture, mNumber) { diff --git a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java index 42defac5bab8..0a884c98402e 100644 --- a/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java +++ b/services/core/java/com/android/server/display/ScreenOffBrightnessSensorController.java @@ -25,8 +25,6 @@ import android.os.Handler; import android.os.PowerManager; import android.util.IndentingPrintWriter; -import com.android.internal.annotations.VisibleForTesting; - import java.io.PrintWriter; /** @@ -79,7 +77,10 @@ public class ScreenOffBrightnessSensorController implements SensorEventListener public void onAccuracyChanged(Sensor sensor, int accuracy) { } - void setLightSensorEnabled(boolean enabled) { + /** + * Changes the state of the associated light sensor + */ + public void setLightSensorEnabled(boolean enabled) { if (enabled && !mRegistered) { // Wait until we get an event from the sensor indicating ready. mRegistered = mSensorManager.registerListener(this, mLightSensor, @@ -92,11 +93,17 @@ public class ScreenOffBrightnessSensorController implements SensorEventListener } } - void stop() { + /** + * Stops the associated sensor, and cleans up the state + */ + public void stop() { setLightSensorEnabled(false); } - float getAutomaticScreenBrightness() { + /** + * Gets the automatic screen brightness based on the ambient lux + */ + public float getAutomaticScreenBrightness() { if (mLastSensorValue < 0 || mLastSensorValue >= mSensorValueToLux.length || (!mRegistered && mClock.uptimeMillis() - mSensorDisableTime > SENSOR_VALUE_VALID_TIME_MILLIS)) { @@ -121,8 +128,7 @@ public class ScreenOffBrightnessSensorController implements SensorEventListener } /** Functional interface for providing time. */ - @VisibleForTesting - interface Clock { + public interface Clock { /** * Returns current time in milliseconds since boot, not counting time spent in deep sleep. */ diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java index bcdb442c3ad3..1a5c79fada55 100644 --- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java +++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java @@ -92,13 +92,15 @@ 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.createVirtualDisplay(name, secure, uniqueId, + requestedRefreshRate); } @Override public void destroyDisplay(IBinder displayToken) { - DisplayControl.destroyDisplay(displayToken); + DisplayControl.destroyVirtualDisplay(displayToken); } }, featureFlags); } @@ -126,7 +128,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 +655,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 +666,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/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java index aa98cd85d38e..607c5d6a88bc 100644 --- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java @@ -392,9 +392,9 @@ final class WifiDisplayAdapter extends DisplayAdapter { float refreshRate = 60.0f; // TODO: get this for real - String name = display.getFriendlyDisplayName(); - String address = display.getDeviceAddress(); - IBinder displayToken = DisplayControl.createDisplay(name, secure); + final String name = display.getFriendlyDisplayName(); + final String address = display.getDeviceAddress(); + IBinder displayToken = DisplayControl.createVirtualDisplay(name, secure); mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height, refreshRate, deviceFlags, address, surface); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED); @@ -631,7 +631,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { mSurface.release(); mSurface = null; } - DisplayControl.destroyDisplay(getDisplayTokenLocked()); + DisplayControl.destroyVirtualDisplay(getDisplayTokenLocked()); } public void setNameLocked(String name) { diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 6a88a766fa07..aa17df6562d8 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -18,7 +18,9 @@ package com.android.server.display.brightness; import android.annotation.Nullable; import android.content.Context; +import android.hardware.SensorManager; import android.hardware.display.DisplayManagerInternal; +import android.os.Handler; import android.os.HandlerExecutor; import android.os.PowerManager; import android.util.IndentingPrintWriter; @@ -31,6 +33,8 @@ import com.android.server.display.AutomaticBrightnessController; import com.android.server.display.BrightnessMappingStrategy; import com.android.server.display.BrightnessSetting; import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStrategy; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.feature.DisplayManagerFlags; @@ -103,7 +107,8 @@ public final class DisplayBrightnessController { // The controller for the automatic brightness level. // TODO(b/265415257): Move to the automatic brightness strategy @Nullable - private AutomaticBrightnessController mAutomaticBrightnessController; + @VisibleForTesting + AutomaticBrightnessController mAutomaticBrightnessController; /** * The constructor of DisplayBrightnessController. @@ -332,14 +337,16 @@ public final class DisplayBrightnessController { } /** - * Set the {@link AutomaticBrightnessController} which is needed to perform nit-to-float-scale - * conversion. - * @param automaticBrightnessController The ABC + * Sets up the auto brightness and the relevant state for the associated display */ - public void setAutomaticBrightnessController( - AutomaticBrightnessController automaticBrightnessController) { - mAutomaticBrightnessController = automaticBrightnessController; - loadNitBasedBrightnessSetting(); + public void setUpAutoBrightness(AutomaticBrightnessController automaticBrightnessController, + SensorManager sensorManager, + DisplayDeviceConfig displayDeviceConfig, Handler handler, + BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled, + int leadDisplayId) { + setAutomaticBrightnessController(automaticBrightnessController); + setUpAutoBrightnessFallbackStrategy(sensorManager, displayDeviceConfig, handler, + brightnessMappingStrategy, isEnabled, leadDisplayId); } /** @@ -404,6 +411,17 @@ public final class DisplayBrightnessController { if (mBrightnessSetting != null) { mBrightnessSetting.unregisterListener(mBrightnessSettingListener); } + AutoBrightnessFallbackStrategy autoBrightnessFallbackStrategy = + getAutoBrightnessFallbackStrategy(); + if (autoBrightnessFallbackStrategy != null) { + autoBrightnessFallbackStrategy.stop(); + } + } + + private AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() { + synchronized (mLock) { + return mDisplayBrightnessStrategySelector.getAutoBrightnessFallbackStrategy(); + } } /** @@ -484,6 +502,33 @@ public final class DisplayBrightnessController { } /** + * Set the {@link AutomaticBrightnessController} which is needed to perform nit-to-float-scale + * conversion. + * @param automaticBrightnessController The ABC + */ + @VisibleForTesting + void setAutomaticBrightnessController( + AutomaticBrightnessController automaticBrightnessController) { + mAutomaticBrightnessController = automaticBrightnessController; + getAutomaticBrightnessStrategy() + .setAutomaticBrightnessController(automaticBrightnessController); + loadNitBasedBrightnessSetting(); + } + + private void setUpAutoBrightnessFallbackStrategy(SensorManager sensorManager, + DisplayDeviceConfig displayDeviceConfig, Handler handler, + BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled, + int leadDisplayId) { + AutoBrightnessFallbackStrategy autoBrightnessFallbackStrategy = + getAutoBrightnessFallbackStrategy(); + if (autoBrightnessFallbackStrategy != null) { + autoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor( + sensorManager, displayDeviceConfig, handler, brightnessMappingStrategy, + isEnabled, leadDisplayId); + } + } + + /** * TODO(b/253226419): Remove once auto-brightness is a fully-functioning strategy. */ private DisplayBrightnessState addAutomaticBrightnessState(DisplayBrightnessState state) { diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 282083f9da20..feec4e6b2259 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -26,11 +26,13 @@ import android.view.Display; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStrategy; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2; import com.android.server.display.brightness.strategy.BoostBrightnessStrategy; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; +import com.android.server.display.brightness.strategy.FallbackBrightnessStrategy; import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy; import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; @@ -81,10 +83,17 @@ public class DisplayBrightnessStrategySelector { @Nullable private final OffloadBrightnessStrategy mOffloadBrightnessStrategy; + @Nullable + private final AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy; + + @Nullable + private final FallbackBrightnessStrategy mFallbackBrightnessStrategy; + // A collective representation of all the strategies that the selector is aware of. This is // non null, but the strategies this is tracking can be null @NonNull - private final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies; + @VisibleForTesting + final DisplayBrightnessStrategy[] mDisplayBrightnessStrategies; @NonNull private final DisplayManagerFlags mDisplayManagerFlags; @@ -113,23 +122,31 @@ public class DisplayBrightnessStrategySelector { mInvalidBrightnessStrategy = injector.getInvalidBrightnessStrategy(); mAutomaticBrightnessStrategy1 = (!mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null - : injector.getAutomaticBrightnessStrategy1(context, displayId); + : injector.getAutomaticBrightnessStrategy1(context, displayId, + mDisplayManagerFlags); mAutomaticBrightnessStrategy2 = (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? null : injector.getAutomaticBrightnessStrategy2(context, displayId); mAutomaticBrightnessStrategy = (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) ? mAutomaticBrightnessStrategy1 : mAutomaticBrightnessStrategy2; + mAutoBrightnessFallbackStrategy = (mDisplayManagerFlags + .isRefactorDisplayPowerControllerEnabled()) + ? injector.getAutoBrightnessFallbackStrategy() : null; if (flags.isDisplayOffloadEnabled()) { mOffloadBrightnessStrategy = injector .getOffloadBrightnessStrategy(mDisplayManagerFlags); } else { mOffloadBrightnessStrategy = null; } + mFallbackBrightnessStrategy = (mDisplayManagerFlags + .isRefactorDisplayPowerControllerEnabled()) + ? injector.getFallbackBrightnessStrategy() : null; mDisplayBrightnessStrategies = new DisplayBrightnessStrategy[]{mInvalidBrightnessStrategy, mScreenOffBrightnessStrategy, mDozeBrightnessStrategy, mFollowerBrightnessStrategy, mBoostBrightnessStrategy, mOverrideBrightnessStrategy, mTemporaryBrightnessStrategy, - mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy}; + mAutomaticBrightnessStrategy1, mOffloadBrightnessStrategy, + mAutoBrightnessFallbackStrategy, mFallbackBrightnessStrategy}; mAllowAutoBrightnessWhileDozingConfig = context.getResources().getBoolean( R.bool.config_allowAutoBrightnessWhileDozing); mOldBrightnessStrategyName = mInvalidBrightnessStrategy.getName(); @@ -168,6 +185,14 @@ public class DisplayBrightnessStrategySelector { && mOffloadBrightnessStrategy != null && BrightnessUtils.isValidBrightnessValue( mOffloadBrightnessStrategy.getOffloadScreenBrightness())) { displayBrightnessStrategy = mOffloadBrightnessStrategy; + } else if (isAutoBrightnessFallbackStrategyValid()) { + displayBrightnessStrategy = mAutoBrightnessFallbackStrategy; + } else { + // This will become the ultimate fallback strategy once the flag has been fully rolled + // out + if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) { + displayBrightnessStrategy = mFallbackBrightnessStrategy; + } } if (mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()) { @@ -210,6 +235,11 @@ public class DisplayBrightnessStrategySelector { return mAllowAutoBrightnessWhileDozingConfig; } + @Nullable + public AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() { + return mAutoBrightnessFallbackStrategy; + } + /** * Dumps the state of this class. */ @@ -229,6 +259,13 @@ public class DisplayBrightnessStrategySelector { } } + private boolean isAutoBrightnessFallbackStrategyValid() { + return mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled() + && mAutoBrightnessFallbackStrategy != null + && getAutomaticBrightnessStrategy().shouldUseAutoBrightness() + && mAutoBrightnessFallbackStrategy.isValid(); + } + private boolean isAutomaticBrightnessStrategyValid( StrategySelectionRequest strategySelectionRequest) { mAutomaticBrightnessStrategy1.setAutoBrightnessState( @@ -245,12 +282,13 @@ public class DisplayBrightnessStrategySelector { DisplayBrightnessStrategy selectedDisplayBrightnessStrategy, StrategySelectionRequest strategySelectionRequest) { return new StrategySelectionNotifyRequest( - strategySelectionRequest.getDisplayPowerRequest(), + strategySelectionRequest.getDisplayPowerRequest(), strategySelectionRequest.getTargetDisplayState(), selectedDisplayBrightnessStrategy, strategySelectionRequest.getLastUserSetScreenBrightness(), strategySelectionRequest.isUserSetBrightnessChanged(), - isAllowAutoBrightnessWhileDozingConfig()); + isAllowAutoBrightnessWhileDozingConfig(), + getAutomaticBrightnessStrategy().shouldUseAutoBrightness()); } private void postProcess(StrategySelectionNotifyRequest strategySelectionNotifyRequest) { @@ -306,8 +344,8 @@ public class DisplayBrightnessStrategySelector { } AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context, - int displayId) { - return new AutomaticBrightnessStrategy(context, displayId); + int displayId, DisplayManagerFlags displayManagerFlags) { + return new AutomaticBrightnessStrategy(context, displayId, displayManagerFlags); } AutomaticBrightnessStrategy2 getAutomaticBrightnessStrategy2(Context context, @@ -319,5 +357,13 @@ public class DisplayBrightnessStrategySelector { DisplayManagerFlags displayManagerFlags) { return new OffloadBrightnessStrategy(displayManagerFlags); } + + AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() { + return new AutoBrightnessFallbackStrategy(/* injector= */ null); + } + + FallbackBrightnessStrategy getFallbackBrightnessStrategy() { + return new FallbackBrightnessStrategy(); + } } } diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java index 6e6c9720bc3a..bfa90e297059 100644 --- a/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java +++ b/services/core/java/com/android/server/display/brightness/StrategySelectionNotifyRequest.java @@ -45,18 +45,22 @@ public final class StrategySelectionNotifyRequest { // True if light sensor is to be used to automatically determine doze screen brightness. private final boolean mAllowAutoBrightnessWhileDozingConfig; + // True if the auto brightness is enabled in the settings + private final boolean mIsAutoBrightnessEnabled; public StrategySelectionNotifyRequest( DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int targetDisplayState, DisplayBrightnessStrategy displayBrightnessStrategy, float lastUserSetScreenBrightness, - boolean userSetBrightnessChanged, boolean allowAutoBrightnessWhileDozingConfig) { + boolean userSetBrightnessChanged, boolean allowAutoBrightnessWhileDozingConfig, + boolean isAutoBrightnessEnabled) { mDisplayPowerRequest = displayPowerRequest; mTargetDisplayState = targetDisplayState; mSelectedDisplayBrightnessStrategy = displayBrightnessStrategy; mLastUserSetScreenBrightness = lastUserSetScreenBrightness; mUserSetBrightnessChanged = userSetBrightnessChanged; mAllowAutoBrightnessWhileDozingConfig = allowAutoBrightnessWhileDozingConfig; + mIsAutoBrightnessEnabled = isAutoBrightnessEnabled; } public DisplayBrightnessStrategy getSelectedDisplayBrightnessStrategy() { @@ -76,14 +80,15 @@ public final class StrategySelectionNotifyRequest { && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged() && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness() && mAllowAutoBrightnessWhileDozingConfig - == other.isAllowAutoBrightnessWhileDozingConfig(); + == other.isAllowAutoBrightnessWhileDozingConfig() + && mIsAutoBrightnessEnabled == other.isAutoBrightnessEnabled(); } @Override public int hashCode() { return Objects.hash(mSelectedDisplayBrightnessStrategy, mDisplayPowerRequest, mTargetDisplayState, mUserSetBrightnessChanged, mLastUserSetScreenBrightness, - mAllowAutoBrightnessWhileDozingConfig); + mAllowAutoBrightnessWhileDozingConfig, mIsAutoBrightnessEnabled); } public float getLastUserSetScreenBrightness() { @@ -106,6 +111,10 @@ public final class StrategySelectionNotifyRequest { return mAllowAutoBrightnessWhileDozingConfig; } + public boolean isAutoBrightnessEnabled() { + return mIsAutoBrightnessEnabled; + } + /** * A utility to stringify a StrategySelectionNotifyRequest */ @@ -116,6 +125,7 @@ public final class StrategySelectionNotifyRequest { + " mSelectedDisplayBrightnessStrategy=" + mSelectedDisplayBrightnessStrategy + " mLastUserSetScreenBrightness=" + mLastUserSetScreenBrightness + " mUserSetBrightnessChanged=" + mUserSetBrightnessChanged - + " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig; + + " mAllowAutoBrightnessWhileDozingConfig=" + mAllowAutoBrightnessWhileDozingConfig + + " mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled; } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java index a46975fb3567..11ef5776a47a 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -285,7 +285,8 @@ public class BrightnessClamperController { List<BrightnessStateModifier> modifiers = new ArrayList<>(); modifiers.add(new DisplayDimModifier(context)); modifiers.add(new BrightnessLowPowerModeModifier()); - if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null) { + if (flags.isEvenDimmerEnabled() && displayDeviceConfig != null + && displayDeviceConfig.isEvenDimmerAvailable()) { modifiers.add(new BrightnessLowLuxModifier(handler, listener, context, displayDeviceConfig)); } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java index a3dfe229eb59..7ba4a4d9c4dd 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowLuxModifier.java @@ -87,9 +87,7 @@ public class BrightnessLowLuxModifier extends BrightnessModifier { mContentResolver, Settings.Secure.EVEN_DIMMER_MIN_NITS, /* def= */ MIN_NITS_DEFAULT, userId); - boolean isActive = Settings.Secure.getFloatForUser(mContentResolver, - Settings.Secure.EVEN_DIMMER_ACTIVATED, - /* def= */ 0, userId) == 1.0f && mAutoBrightnessEnabled; + boolean isActive = isSettingEnabled() && mAutoBrightnessEnabled; float luxBasedNitsLowerBound = mDisplayDeviceConfig.getMinNitsFromLux(mAmbientLux); @@ -202,6 +200,17 @@ public class BrightnessLowLuxModifier extends BrightnessModifier { pw.println(" mMinNitsAllowed=" + mMinNitsAllowed); } + /** + * Defaults to true, on devices where setting is unset. + * + * @return if setting indicates feature is enabled + */ + private boolean isSettingEnabled() { + return Settings.Secure.getFloatForUser(mContentResolver, + Settings.Secure.EVEN_DIMMER_ACTIVATED, + /* def= */ 1.0f, UserHandle.USER_CURRENT) == 1.0f; + } + private float getBrightnessFromNits(float nits) { return mDisplayDeviceConfig.getBrightnessFromBacklight( mDisplayDeviceConfig.getBacklightFromNits(nits)); diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java new file mode 100644 index 000000000000..16bf177f191b --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategy.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.strategy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.SystemClock; +import android.util.IndentingPrintWriter; +import android.view.Display; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.BrightnessMappingStrategy; +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.ScreenOffBrightnessSensorController; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.BrightnessUtils; +import com.android.server.display.brightness.StrategyExecutionRequest; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; +import com.android.server.display.layout.Layout; +import com.android.server.display.utils.SensorUtils; + +import java.io.PrintWriter; + +/** + * This strategy is used when the screen has just turned ON, with auto-brightness ON but there is + * no valid lux values available yet. In such a case, if configured, we set the brightness state + * from this + */ +public final class AutoBrightnessFallbackStrategy implements DisplayBrightnessStrategy { + + @Nullable + private ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController; + @VisibleForTesting + @Nullable + Sensor mScreenOffBrightnessSensor; + + // Indicates if the associated LogicalDisplay is enabled or not. + private boolean mIsEnabled; + + // Represents if the associated display is a lead display or not. If not, the variable + // represents the lead display ID + private int mLeadDisplayId; + + @NonNull + private final Injector mInjector; + + public AutoBrightnessFallbackStrategy(Injector injector) { + mInjector = (injector == null) ? new RealInjector() : injector; + } + + @Override + public DisplayBrightnessState updateBrightness( + StrategyExecutionRequest strategyExecutionRequest) { + assert mScreenOffBrightnessSensorController != null; + float brightness = mScreenOffBrightnessSensorController.getAutomaticScreenBrightness(); + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR); + return new DisplayBrightnessState.Builder() + .setBrightness(brightness) + .setSdrBrightness(brightness) + .setBrightnessReason(brightnessReason) + .setDisplayBrightnessStrategyName(getName()) + .setShouldUpdateScreenBrightnessSetting(brightness + != strategyExecutionRequest.getCurrentScreenBrightness()) + .build(); + } + + @NonNull + @Override + public String getName() { + return "AutoBrightnessFallbackStrategy"; + } + + @Override + public int getReason() { + return BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR; + } + + @Override + public void dump(PrintWriter writer) { + writer.println("AutoBrightnessFallbackStrategy:"); + writer.println(" mLeadDisplayId=" + mLeadDisplayId); + writer.println(" mIsEnabled=" + mIsEnabled); + if (mScreenOffBrightnessSensorController != null) { + IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " "); + mScreenOffBrightnessSensorController.dump(ipw); + } + } + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + if (mScreenOffBrightnessSensorController != null) { + int targetDisplayState = strategySelectionNotifyRequest.getTargetDisplayState(); + mScreenOffBrightnessSensorController.setLightSensorEnabled( + strategySelectionNotifyRequest.isAutoBrightnessEnabled() && mIsEnabled + && (targetDisplayState == Display.STATE_OFF + || (targetDisplayState == Display.STATE_DOZE + && !strategySelectionNotifyRequest + .isAllowAutoBrightnessWhileDozingConfig())) + && mLeadDisplayId == Layout.NO_LEAD_DISPLAY); + } + } + + /** + * Gets the associated ScreenOffBrightnessSensorController, controlling the brightness when + * auto-brightness is enabled, but the lux is not valid yet. + */ + public ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController() { + return mScreenOffBrightnessSensorController; + } + + /** + * Sets up the auto brightness fallback sensor + */ + public void setupAutoBrightnessFallbackSensor(SensorManager sensorManager, + DisplayDeviceConfig displayDeviceConfig, Handler handler, + BrightnessMappingStrategy brightnessMappingStrategy, boolean isEnabled, + int leadDisplayId) { + mIsEnabled = isEnabled; + mLeadDisplayId = leadDisplayId; + if (mScreenOffBrightnessSensorController != null) { + mScreenOffBrightnessSensorController.stop(); + mScreenOffBrightnessSensorController = null; + } + loadScreenOffBrightnessSensor(sensorManager, displayDeviceConfig); + int[] sensorValueToLux = displayDeviceConfig.getScreenOffBrightnessSensorValueToLux(); + if (mScreenOffBrightnessSensor != null && sensorValueToLux != null) { + mScreenOffBrightnessSensorController = + mInjector.getScreenOffBrightnessSensorController( + sensorManager, + mScreenOffBrightnessSensor, + handler, + SystemClock::uptimeMillis, + sensorValueToLux, + brightnessMappingStrategy); + } + } + + /** + * Stops the associated ScreenOffBrightnessSensorController responsible for managing the + * brightness when this strategy is selected + */ + public void stop() { + if (mScreenOffBrightnessSensorController != null) { + mScreenOffBrightnessSensorController.stop(); + } + } + + /** + * Checks if the strategy is valid, based on its internal state. Note that there can still be + * external factors like auto-brightness not being enabled because of which this strategy is not + * selected + */ + public boolean isValid() { + return mScreenOffBrightnessSensorController != null + && BrightnessUtils.isValidBrightnessValue( + mScreenOffBrightnessSensorController.getAutomaticScreenBrightness()); + } + + private void loadScreenOffBrightnessSensor(SensorManager sensorManager, + DisplayDeviceConfig displayDeviceConfig) { + mScreenOffBrightnessSensor = mInjector.getScreenOffBrightnessSensor(sensorManager, + displayDeviceConfig); + } + + + @VisibleForTesting + interface Injector { + Sensor getScreenOffBrightnessSensor(SensorManager sensorManager, + DisplayDeviceConfig displayDeviceConfig); + + ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController( + SensorManager sensorManager, + Sensor lightSensor, + Handler handler, + ScreenOffBrightnessSensorController.Clock clock, + int[] sensorValueToLux, + BrightnessMappingStrategy brightnessMapper); + } + + static class RealInjector implements Injector { + @Override + public Sensor getScreenOffBrightnessSensor(SensorManager sensorManager, + DisplayDeviceConfig displayDeviceConfig) { + return SensorUtils.findSensor(sensorManager, + displayDeviceConfig.getScreenOffBrightnessSensor(), SensorUtils.NO_FALLBACK); + } + + @Override + public ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController( + SensorManager sensorManager, Sensor lightSensor, Handler handler, + ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux, + BrightnessMappingStrategy brightnessMapper) { + return new ScreenOffBrightnessSensorController( + sensorManager, lightSensor, handler, clock, sensorValueToLux, brightnessMapper); + } + } +} diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java index 5c4fa842c383..37b693190f7f 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java @@ -17,6 +17,9 @@ package com.android.server.display.brightness.strategy; import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; +import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; + import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessConfiguration; @@ -33,6 +36,7 @@ import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.brightness.StrategyExecutionRequest; import com.android.server.display.brightness.StrategySelectionNotifyRequest; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; @@ -98,19 +102,24 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 private Injector mInjector; + private DisplayManagerFlags mDisplayManagerFlags; + @VisibleForTesting - AutomaticBrightnessStrategy(Context context, int displayId, Injector injector) { + AutomaticBrightnessStrategy(Context context, int displayId, Injector injector, + DisplayManagerFlags displayManagerFlags) { super(context, displayId); mContext = context; mDisplayId = displayId; mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting(); mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mDisplayManagerFlags = displayManagerFlags; mInjector = (injector == null) ? new RealInjector() : injector; } - public AutomaticBrightnessStrategy(Context context, int displayId) { - this(context, displayId, null); + public AutomaticBrightnessStrategy(Context context, int displayId, + DisplayManagerFlags displayManagerFlags) { + this(context, displayId, null, displayManagerFlags); } /** @@ -120,6 +129,7 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 public void setAutoBrightnessState(int targetDisplayState, boolean allowAutoBrightnessWhileDozingConfig, int brightnessReason, int policy, float lastUserSetScreenBrightness, boolean userSetBrightnessChanged) { + switchMode(targetDisplayState); final boolean autoBrightnessEnabledInDoze = allowAutoBrightnessWhileDozingConfig && policy == POLICY_DOZE; mIsAutoBrightnessEnabled = shouldUseAutoBrightness() @@ -155,9 +165,8 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 public boolean isAutoBrightnessValid() { boolean isValid = false; if (isAutoBrightnessEnabled()) { - float brightness = (mAutomaticBrightnessController != null) - ? mAutomaticBrightnessController.getAutomaticScreenBrightness(null) - : PowerManager.BRIGHTNESS_INVALID_FLOAT; + float brightness = getAutomaticScreenBrightness(null, + /* isAutomaticBrightnessAdjusted = */ false); if (BrightnessUtils.isValidBrightnessValue(brightness) || brightness == PowerManager.BRIGHTNESS_OFF_FLOAT) { isValid = true; @@ -264,7 +273,12 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 BrightnessReason brightnessReason = new BrightnessReason(); brightnessReason.setReason(BrightnessReason.REASON_AUTOMATIC); BrightnessEvent brightnessEvent = mInjector.getBrightnessEvent(mDisplayId); - float brightness = getAutomaticScreenBrightness(brightnessEvent); + + // AutoBrightness adjustments were already applied while checking the validity of this + // strategy. Reapplying them again will result in incorrect adjustment reason flags as we + // might end up assuming no adjustments are applied + float brightness = getAutomaticScreenBrightness(brightnessEvent, + /* isAutomaticBrightnessAdjusted = */ true); return new DisplayBrightnessState.Builder() .setBrightness(brightness) .setSdrBrightness(brightness) @@ -345,11 +359,14 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 * @param brightnessEvent Event object to populate with details about why the specific * brightness was chosen. */ - public float getAutomaticScreenBrightness(BrightnessEvent brightnessEvent) { + public float getAutomaticScreenBrightness(BrightnessEvent brightnessEvent, + boolean isAutomaticBrightnessAdjusted) { float brightness = (mAutomaticBrightnessController != null) ? mAutomaticBrightnessController.getAutomaticScreenBrightness(brightnessEvent) : PowerManager.BRIGHTNESS_INVALID_FLOAT; - adjustAutomaticBrightnessStateIfValid(brightness); + if (!isAutomaticBrightnessAdjusted) { + adjustAutomaticBrightnessStateIfValid(brightness); + } return brightness; } @@ -360,11 +377,11 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 * @param brightnessEvent Event object to populate with details about why the specific * brightness was chosen. */ - public float getAutomaticScreenBrightnessBasedOnLastObservedLux( + public float getAutomaticScreenBrightnessBasedOnLastUsedLux( BrightnessEvent brightnessEvent) { float brightness = (mAutomaticBrightnessController != null) ? mAutomaticBrightnessController - .getAutomaticScreenBrightnessBasedOnLastObservedLux(brightnessEvent) + .getAutomaticScreenBrightnessBasedOnLastUsedLux(brightnessEvent) : PowerManager.BRIGHTNESS_INVALID_FLOAT; adjustAutomaticBrightnessStateIfValid(brightness); return brightness; @@ -479,6 +496,16 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 } } + + private void switchMode(int state) { + if (mDisplayManagerFlags.areAutoBrightnessModesEnabled() + && mAutomaticBrightnessController != null + && !mAutomaticBrightnessController.isInIdleMode()) { + mAutomaticBrightnessController.switchMode(Display.isDozeState(state) + ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT); + } + } + /** * Evaluates if there are any temporary auto-brightness adjustments which is not applied yet. * Temporary brightness adjustments happen when the user moves the brightness slider in the diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java index 25e8b2392665..58670c97e944 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java +++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2.java @@ -279,11 +279,11 @@ public class AutomaticBrightnessStrategy2 { * @param brightnessEvent Event object to populate with details about why the specific * brightness was chosen. */ - public float getAutomaticScreenBrightnessBasedOnLastObservedLux( + public float getAutomaticScreenBrightnessBasedOnLastUsedLux( BrightnessEvent brightnessEvent) { float brightness = (mAutomaticBrightnessController != null) ? mAutomaticBrightnessController - .getAutomaticScreenBrightnessBasedOnLastObservedLux(brightnessEvent) + .getAutomaticScreenBrightnessBasedOnLastUsedLux(brightnessEvent) : PowerManager.BRIGHTNESS_INVALID_FLOAT; adjustAutomaticBrightnessStateIfValid(brightness); return brightness; diff --git a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java index 504683a55735..7b2f2b9d307b 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java +++ b/services/core/java/com/android/server/display/brightness/strategy/DisplayBrightnessStrategyConstants.java @@ -18,4 +18,5 @@ package com.android.server.display.brightness.strategy; public class DisplayBrightnessStrategyConstants { static final String INVALID_BRIGHTNESS_STRATEGY_NAME = "InvalidBrightnessStrategy"; + public static final String FALLBACK_BRIGHTNESS_STRATEGY_NAME = "FallbackBrightnessStrategy"; } diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java new file mode 100644 index 000000000000..3463649aa000 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.strategy; + +import android.annotation.NonNull; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.StrategyExecutionRequest; +import com.android.server.display.brightness.StrategySelectionNotifyRequest; + +import java.io.PrintWriter; + +/** + * Manages the brightness of the associated display when no other strategy qualifies for + * setting up the brightness state. This strategy is also being used for evaluating the + * display brightness state when we have a manually set brightness. This is a temporary state, and + * the logic for evaluating the manual brightness will be moved to a separate strategy + */ +public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{ + @Override + public DisplayBrightnessState updateBrightness( + StrategyExecutionRequest strategyExecutionRequest) { + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_MANUAL); + return new DisplayBrightnessState.Builder() + .setBrightness(strategyExecutionRequest.getCurrentScreenBrightness()) + .setSdrBrightness(strategyExecutionRequest.getCurrentScreenBrightness()) + .setBrightnessReason(brightnessReason) + .setDisplayBrightnessStrategyName(getName()) + // The fallback brightness might change due to clamping. Make sure we tell the rest + // of the system by updating the setting + .setShouldUpdateScreenBrightnessSetting(true) + .build(); + } + + @NonNull + @Override + public String getName() { + return DisplayBrightnessStrategyConstants.FALLBACK_BRIGHTNESS_STRATEGY_NAME; + } + + @Override + public int getReason() { + return BrightnessReason.REASON_MANUAL; + } + + @Override + public void dump(PrintWriter writer) { + + } + + @Override + public void strategySelectionPostProcessor( + StrategySelectionNotifyRequest strategySelectionNotifyRequest) { + + } +} diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java index 0bb93a96d9df..3883604b7134 100644 --- a/services/core/java/com/android/server/display/color/ColorDisplayService.java +++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java @@ -78,6 +78,7 @@ import com.android.internal.util.DumpUtils; import com.android.server.DisplayThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.accessibility.Flags; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.twilight.TwilightListener; import com.android.server.twilight.TwilightManager; @@ -356,6 +357,11 @@ public final class ColorDisplayService extends SystemService { case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER: onAccessibilityDaltonizerChanged(); break; + case Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL: + if (Flags.enableColorCorrectionSaturation()) { + onAccessibilityDaltonizerChanged(); + } + break; case Secure.DISPLAY_WHITE_BALANCE_ENABLED: updateDisplayWhiteBalanceStatus(); break; @@ -398,6 +404,11 @@ public final class ColorDisplayService extends SystemService { false /* notifyForDescendants */, mContentObserver, mCurrentUser); cr.registerContentObserver(Secure.getUriFor(Secure.REDUCE_BRIGHT_COLORS_LEVEL), false /* notifyForDescendants */, mContentObserver, mCurrentUser); + if (Flags.enableColorCorrectionSaturation()) { + cr.registerContentObserver( + Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL), + false /* notifyForDescendants */, mContentObserver, mCurrentUser); + } // Apply the accessibility settings first, since they override most other settings. onAccessibilityInversionChanged(); @@ -597,21 +608,31 @@ public final class ColorDisplayService extends SystemService { if (mCurrentUser == UserHandle.USER_NULL) { return; } + var contentResolver = getContext().getContentResolver(); final int daltonizerMode = isAccessiblityDaltonizerEnabled() - ? Secure.getIntForUser(getContext().getContentResolver(), - Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, - AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser) + ? Secure.getIntForUser(contentResolver, + Secure.ACCESSIBILITY_DISPLAY_DALTONIZER, + AccessibilityManager.DALTONIZER_CORRECT_DEUTERANOMALY, mCurrentUser) : AccessibilityManager.DALTONIZER_DISABLED; + int saturation = NOT_SET; + if (Flags.enableColorCorrectionSaturation()) { + saturation = Secure.getIntForUser( + contentResolver, + Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_SATURATION_LEVEL, + NOT_SET, + mCurrentUser); + } + final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class); if (daltonizerMode == AccessibilityManager.DALTONIZER_SIMULATE_MONOCHROMACY) { // Monochromacy isn't supported by the native Daltonizer implementation; use grayscale. dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, MATRIX_GRAYSCALE); - dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED); + dtm.setDaltonizerMode(AccessibilityManager.DALTONIZER_DISABLED, saturation); } else { dtm.setColorMatrix(DisplayTransformManager.LEVEL_COLOR_MATRIX_GRAYSCALE, null); - dtm.setDaltonizerMode(daltonizerMode); + dtm.setDaltonizerMode(daltonizerMode, saturation); } } diff --git a/services/core/java/com/android/server/display/color/DisplayTransformManager.java b/services/core/java/com/android/server/display/color/DisplayTransformManager.java index 0dba9e1b0af1..a76c427bec0e 100644 --- a/services/core/java/com/android/server/display/color/DisplayTransformManager.java +++ b/services/core/java/com/android/server/display/color/DisplayTransformManager.java @@ -16,6 +16,7 @@ package com.android.server.display.color; +import android.annotation.IntRange; import android.app.ActivityTaskManager; import android.hardware.display.ColorDisplayManager; import android.opengl.Matrix; @@ -111,9 +112,15 @@ public class DisplayTransformManager { /** * Lock used for synchronize access to {@link #mDaltonizerMode}. */ - private final Object mDaltonizerModeLock = new Object(); + @VisibleForTesting + final Object mDaltonizerModeLock = new Object(); + @VisibleForTesting + @GuardedBy("mDaltonizerModeLock") + int mDaltonizerMode = -1; + + @VisibleForTesting @GuardedBy("mDaltonizerModeLock") - private int mDaltonizerMode = -1; + int mDaltonizerLevel = -1; private static final IBinder sFlinger = ServiceManager.getService(SURFACE_FLINGER); @@ -168,12 +175,15 @@ public class DisplayTransformManager { * various types of color blindness. * * @param mode the new Daltonization mode, or -1 to disable + * @param level the level of saturation for color correction [-1,10] inclusive. -1 for when + * it is not set. */ - public void setDaltonizerMode(int mode) { + public void setDaltonizerMode(int mode, @IntRange(from = -1, to = 10) int level) { synchronized (mDaltonizerModeLock) { - if (mDaltonizerMode != mode) { + if (mDaltonizerMode != mode || mDaltonizerLevel != level) { mDaltonizerMode = mode; - applyDaltonizerMode(mode); + mDaltonizerLevel = level; + applyDaltonizerMode(mode, level); } } } @@ -223,10 +233,11 @@ public class DisplayTransformManager { /** * Propagates the provided Daltonization mode to the SurfaceFlinger. */ - private static void applyDaltonizerMode(int mode) { + private static void applyDaltonizerMode(int mode, int level) { final Parcel data = Parcel.obtain(); data.writeInterfaceToken("android.ui.ISurfaceComposer"); data.writeInt(mode); + data.writeInt(level); try { sFlinger.transact(SURFACE_FLINGER_TRANSACTION_DALTONIZER, data, null, 0); } catch (RemoteException ex) { diff --git a/services/core/java/com/android/server/display/config/RefreshRateData.java b/services/core/java/com/android/server/display/config/RefreshRateData.java index b186fd57e31f..d7ed904e398d 100644 --- a/services/core/java/com/android/server/display/config/RefreshRateData.java +++ b/services/core/java/com/android/server/display/config/RefreshRateData.java @@ -20,6 +20,10 @@ import android.annotation.Nullable; import android.content.res.Resources; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Collections; +import java.util.List; /** * RefreshRates config for display @@ -58,12 +62,17 @@ public class RefreshRateData { */ public final int defaultRefreshRateInHbmSunlight; + public final List<SupportedModeData> lowPowerSupportedModes; + + @VisibleForTesting public RefreshRateData(int defaultRefreshRate, int defaultPeakRefreshRate, - int defaultRefreshRateInHbmHdr, int defaultRefreshRateInHbmSunlight) { + int defaultRefreshRateInHbmHdr, int defaultRefreshRateInHbmSunlight, + List<SupportedModeData> lowPowerSupportedModes) { this.defaultRefreshRate = defaultRefreshRate; this.defaultPeakRefreshRate = defaultPeakRefreshRate; this.defaultRefreshRateInHbmHdr = defaultRefreshRateInHbmHdr; this.defaultRefreshRateInHbmSunlight = defaultRefreshRateInHbmSunlight; + this.lowPowerSupportedModes = Collections.unmodifiableList(lowPowerSupportedModes); } @@ -71,9 +80,10 @@ public class RefreshRateData { public String toString() { return "RefreshRateData {" + "defaultRefreshRate: " + defaultRefreshRate - + "defaultPeakRefreshRate: " + defaultPeakRefreshRate - + "defaultRefreshRateInHbmHdr: " + defaultRefreshRateInHbmHdr - + "defaultRefreshRateInHbmSunlight: " + defaultRefreshRateInHbmSunlight + + ", defaultPeakRefreshRate: " + defaultPeakRefreshRate + + ", defaultRefreshRateInHbmHdr: " + defaultRefreshRateInHbmHdr + + ", defaultRefreshRateInHbmSunlight: " + defaultRefreshRateInHbmSunlight + + ", lowPowerSupportedModes=" + lowPowerSupportedModes + "} "; } @@ -90,8 +100,13 @@ public class RefreshRateData { int defaultRefreshRateInHbmSunlight = loadDefaultRefreshRateInHbmSunlight( refreshRateConfigs, resources); + NonNegativeFloatToFloatMap modes = + refreshRateConfigs == null ? null : refreshRateConfigs.getLowPowerSupportedModes(); + List<SupportedModeData> lowPowerSupportedModes = SupportedModeData.load(modes); + return new RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate, - defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight); + defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight, + lowPowerSupportedModes); } private static int loadDefaultRefreshRate( diff --git a/services/core/java/com/android/server/display/config/SensorData.java b/services/core/java/com/android/server/display/config/SensorData.java index 6ad13c3b8a40..8bfc4a3ad77a 100644 --- a/services/core/java/com/android/server/display/config/SensorData.java +++ b/services/core/java/com/android/server/display/config/SensorData.java @@ -24,7 +24,6 @@ import android.text.TextUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.feature.DisplayManagerFlags; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -42,7 +41,7 @@ public class SensorData { public final String name; public final float minRefreshRate; public final float maxRefreshRate; - public final List<SupportedMode> supportedModes; + public final List<SupportedModeData> supportedModes; @VisibleForTesting public SensorData() { @@ -61,7 +60,7 @@ public class SensorData { @VisibleForTesting public SensorData(String type, String name, float minRefreshRate, float maxRefreshRate, - List<SupportedMode> supportedModes) { + List<SupportedModeData> supportedModes) { this.type = type; this.name = name; this.minRefreshRate = minRefreshRate; @@ -214,26 +213,11 @@ public class SensorData { minRefreshRate = rr.getMinimum().floatValue(); maxRefreshRate = rr.getMaximum().floatValue(); } - ArrayList<SupportedMode> supportedModes = new ArrayList<>(); - NonNegativeFloatToFloatMap configSupportedModes = sensorDetails.getSupportedModes(); - if (configSupportedModes != null) { - for (NonNegativeFloatToFloatPoint supportedMode : configSupportedModes.getPoint()) { - supportedModes.add(new SupportedMode(supportedMode.getFirst().floatValue(), - supportedMode.getSecond().floatValue())); - } - } + List<SupportedModeData> supportedModes = SupportedModeData.load( + sensorDetails.getSupportedModes()); return new SensorData(sensorDetails.getType(), sensorDetails.getName(), minRefreshRate, maxRefreshRate, supportedModes); } - public static class SupportedMode { - public final float refreshRate; - public final float vsyncRate; - - public SupportedMode(float refreshRate, float vsyncRate) { - this.refreshRate = refreshRate; - this.vsyncRate = vsyncRate; - } - } } diff --git a/services/core/java/com/android/server/display/config/SupportedModeData.java b/services/core/java/com/android/server/display/config/SupportedModeData.java new file mode 100644 index 000000000000..3c82884e1024 --- /dev/null +++ b/services/core/java/com/android/server/display/config/SupportedModeData.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.config; + +import android.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Supported display mode data. Display mode is uniquely identified by refreshRate-vsync pair + */ +public class SupportedModeData { + public final float refreshRate; + public final float vsyncRate; + + public SupportedModeData(float refreshRate, float vsyncRate) { + this.refreshRate = refreshRate; + this.vsyncRate = vsyncRate; + } + + @Override + public String toString() { + return "SupportedModeData{" + + "refreshRate= " + refreshRate + + ", vsyncRate= " + vsyncRate + + '}'; + } + + static List<SupportedModeData> load(@Nullable NonNegativeFloatToFloatMap configMap) { + ArrayList<SupportedModeData> supportedModes = new ArrayList<>(); + if (configMap != null) { + for (NonNegativeFloatToFloatPoint supportedMode : configMap.getPoint()) { + supportedModes.add(new SupportedModeData(supportedMode.getFirst().floatValue(), + supportedMode.getSecond().floatValue())); + } + } + return supportedModes; + } +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 91bd80eb9037..846ee238499a 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -78,6 +78,7 @@ import com.android.server.LocalServices; import com.android.server.display.DisplayDeviceConfig; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.RefreshRateData; +import com.android.server.display.config.SupportedModeData; import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.utils.AmbientFilter; @@ -451,15 +452,6 @@ public class DisplayModeDirector { return config != null && config.isVrrSupportEnabled(); } - private boolean isVrrSupportedByAnyDisplayLocked() { - for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) { - if (mDisplayDeviceConfigByDisplay.valueAt(i).isVrrSupportEnabled()) { - return true; - } - } - return false; - } - /** * Sets the desiredDisplayModeSpecsListener for changes to display mode and refresh rate ranges. */ @@ -939,18 +931,44 @@ public class DisplayModeDirector { private final Uri mMatchContentFrameRateSetting = Settings.Secure.getUriFor(Settings.Secure.MATCH_CONTENT_FRAME_RATE); - private final boolean mVsynLowPowerVoteEnabled; + private final boolean mVsyncLowPowerVoteEnabled; private final boolean mPeakRefreshRatePhysicalLimitEnabled; private final Context mContext; + private final Handler mHandler; private float mDefaultPeakRefreshRate; private float mDefaultRefreshRate; + private boolean mIsLowPower = false; + + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) { + synchronized (mLock) { + updateLowPowerModeAllowedModesLocked(); + } + } + + @Override + public void onDisplayRemoved(int displayId) { + mVotesStorage.updateVote(displayId, Vote.PRIORITY_LOW_POWER_MODE_MODES, + null); + } + + @Override + public void onDisplayChanged(int displayId) { + synchronized (mLock) { + updateLowPowerModeAllowedModesLocked(); + } + } + }; SettingsObserver(@NonNull Context context, @NonNull Handler handler, DisplayManagerFlags flags) { super(handler); mContext = context; - mVsynLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled(); + mHandler = handler; + mVsyncLowPowerVoteEnabled = flags.isVsyncLowPowerVoteEnabled(); mPeakRefreshRatePhysicalLimitEnabled = flags.isPeakRefreshRatePhysicalLimitEnabled(); // We don't want to load from the DeviceConfig while constructing since this leads to // a spike in the latency of DisplayManagerService startup. This happens because @@ -983,6 +1001,7 @@ public class DisplayModeDirector { UserHandle.USER_SYSTEM); cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, this); + mInjector.registerDisplayListener(mDisplayListener, mHandler); float deviceConfigDefaultPeakRefresh = mConfigParameterProvider.getPeakRefreshRateDefault(); @@ -995,6 +1014,7 @@ public class DisplayModeDirector { updateLowPowerModeSettingLocked(); updateModeSwitchingTypeSettingLocked(); } + } public void setDefaultRefreshRate(float refreshRate) { @@ -1061,23 +1081,36 @@ public class DisplayModeDirector { } private void updateLowPowerModeSettingLocked() { - boolean inLowPowerMode = Settings.Global.getInt(mContext.getContentResolver(), + mIsLowPower = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0 /*default*/) != 0; final Vote vote; - if (inLowPowerMode && mVsynLowPowerVoteEnabled && isVrrSupportedByAnyDisplayLocked()) { - vote = Vote.forSupportedRefreshRates(List.of( - new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, - /* vsyncRate= */ 240f), - new SupportedRefreshRatesVote.RefreshRates(/* peakRefreshRate= */ 60f, - /* vsyncRate= */ 60f) - )); - } else if (inLowPowerMode) { + if (mIsLowPower) { vote = Vote.forRenderFrameRates(0f, 60f); } else { vote = null; } - mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE, vote); - mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode); + mVotesStorage.updateGlobalVote(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, vote); + mBrightnessObserver.onLowPowerModeEnabledLocked(mIsLowPower); + updateLowPowerModeAllowedModesLocked(); + } + + private void updateLowPowerModeAllowedModesLocked() { + if (!mVsyncLowPowerVoteEnabled) { + return; + } + if (mIsLowPower) { + for (int i = 0; i < mDisplayDeviceConfigByDisplay.size(); i++) { + DisplayDeviceConfig config = mDisplayDeviceConfigByDisplay.valueAt(i); + List<SupportedModeData> supportedModes = config + .getRefreshRateData().lowPowerSupportedModes; + mVotesStorage.updateVote( + mDisplayDeviceConfigByDisplay.keyAt(i), + Vote.PRIORITY_LOW_POWER_MODE_MODES, + Vote.forSupportedRefreshRates(supportedModes)); + } + } else { + mVotesStorage.removeAllVotesForPriority(Vote.PRIORITY_LOW_POWER_MODE_MODES); + } } /** diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java index ddb334ee1a9d..8167c1fe1e91 100644 --- a/services/core/java/com/android/server/display/mode/Vote.java +++ b/services/core/java/com/android/server/display/mode/Vote.java @@ -18,6 +18,9 @@ package com.android.server.display.mode; import android.annotation.NonNull; +import com.android.server.display.config.SupportedModeData; + +import java.util.ArrayList; import java.util.List; interface Vote { @@ -102,9 +105,15 @@ interface Vote { // For internal application to limit display modes to specific ids int PRIORITY_SYSTEM_REQUESTED_MODES = 14; - // LOW_POWER_MODE force the render frame rate to [0, 60HZ] if + // PRIORITY_LOW_POWER_MODE_MODES limits display modes to specific refreshRate-vsync pairs if + // Settings.Global.LOW_POWER_MODE is on. + // Lower priority that PRIORITY_LOW_POWER_MODE_RENDER_RATE and if discarded (due to other + // higher priority votes), render rate limit can still apply + int PRIORITY_LOW_POWER_MODE_MODES = 14; + + // PRIORITY_LOW_POWER_MODE_RENDER_RATE force the render frame rate to [0, 60HZ] if // Settings.Global.LOW_POWER_MODE is on. - int PRIORITY_LOW_POWER_MODE = 15; + int PRIORITY_LOW_POWER_MODE_RENDER_RATE = 15; // PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the // higher priority voters' result is a range, it will fix the rate to a single choice. @@ -177,22 +186,26 @@ interface Vote { return new BaseModeRefreshRateVote(baseModeRefreshRate); } - static Vote forSupportedRefreshRates( - List<SupportedRefreshRatesVote.RefreshRates> refreshRates) { - return new SupportedRefreshRatesVote(refreshRates); + static Vote forSupportedRefreshRates(List<SupportedModeData> supportedModes) { + if (supportedModes.isEmpty()) { + return null; + } + List<SupportedRefreshRatesVote.RefreshRates> rates = new ArrayList<>(); + for (SupportedModeData data : supportedModes) { + rates.add(new SupportedRefreshRatesVote.RefreshRates(data.refreshRate, data.vsyncRate)); + } + return new SupportedRefreshRatesVote(rates); } static Vote forSupportedModes(List<Integer> modeIds) { return new SupportedModesVote(modeIds); } - - static Vote forSupportedRefreshRatesAndDisableSwitching( List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates) { return new CombinedVote( List.of(forDisableRefreshRateSwitching(), - forSupportedRefreshRates(supportedRefreshRates))); + new SupportedRefreshRatesVote(supportedRefreshRates))); } static String priorityToString(int priority) { @@ -213,8 +226,10 @@ interface Vote { return "PRIORITY_HIGH_BRIGHTNESS_MODE"; case PRIORITY_PROXIMITY: return "PRIORITY_PROXIMITY"; - case PRIORITY_LOW_POWER_MODE: - return "PRIORITY_LOW_POWER_MODE"; + case PRIORITY_LOW_POWER_MODE_MODES: + return "PRIORITY_LOW_POWER_MODE_MODES"; + case PRIORITY_LOW_POWER_MODE_RENDER_RATE: + return "PRIORITY_LOW_POWER_MODE_RENDER_RATE"; case PRIORITY_SKIN_TEMPERATURE: return "PRIORITY_SKIN_TEMPERATURE"; case PRIORITY_UDFPS: @@ -227,6 +242,8 @@ interface Vote { return "PRIORITY_LIMIT_MODE"; case PRIORITY_SYNCHRONIZED_REFRESH_RATE: return "PRIORITY_SYNCHRONIZED_REFRESH_RATE"; + case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE: + return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE"; case PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE: return "PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE"; case PRIORITY_AUTH_OPTIMIZER_RENDER_FRAME_RATE: diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 816242df639d..c6aef7fb3540 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -249,14 +249,14 @@ final class DreamController { mCurrentDream.mAppTask = appTask; } - void setDreamHasFocus(boolean hasFocus) { + void setDreamIsObscured(boolean isObscured) { if (mCurrentDream != null) { - mCurrentDream.mDreamHasFocus = hasFocus; + mCurrentDream.mDreamIsObscured = isObscured; } } - boolean dreamHasFocus() { - return mCurrentDream != null && mCurrentDream.mDreamHasFocus; + boolean dreamIsFrontmost() { + return mCurrentDream != null && mCurrentDream.dreamIsFrontmost(); } /** @@ -451,7 +451,7 @@ final class DreamController { private String mStopReason; private long mDreamStartTime; public boolean mWakingGently; - public boolean mDreamHasFocus; + private boolean mDreamIsObscured; private final Runnable mStopPreviousDreamsIfNeeded = this::stopPreviousDreamsIfNeeded; private final Runnable mReleaseWakeLockIfNeeded = this::releaseWakeLockIfNeeded; @@ -549,5 +549,9 @@ final class DreamController { mHandler.removeCallbacks(mReleaseWakeLockIfNeeded); } } + + boolean dreamIsFrontmost() { + return !mDreamIsObscured; + } } } diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 2def5aed2478..18a9986d34ba 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -20,7 +20,7 @@ import static android.Manifest.permission.BIND_DREAM_SERVICE; import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.service.dreams.Flags.dreamTracksFocus; +import static android.service.dreams.Flags.dreamHandlesBeingObscured; import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID; @@ -428,7 +428,7 @@ public final class DreamManagerService extends SystemService { // Can't start dreaming if we are already dreaming and the dream has focus. If we are // dreaming but the dream does not have focus, then the dream can be brought to the // front so it does have focus. - if (isScreenOn && isDreamingInternal() && dreamHasFocus()) { + if (isScreenOn && isDreamingInternal() && dreamIsFrontmost()) { return false; } @@ -463,9 +463,10 @@ public final class DreamManagerService extends SystemService { } } - private boolean dreamHasFocus() { - // Dreams always had focus before they were able to track it. - return !dreamTracksFocus() || mController.dreamHasFocus(); + private boolean dreamIsFrontmost() { + // Dreams were always considered frontmost before they began tracking whether they are + // obscured. + return !dreamHandlesBeingObscured() || mController.dreamIsFrontmost(); } protected void requestStartDreamFromShell() { @@ -473,7 +474,7 @@ public final class DreamManagerService extends SystemService { } private void requestDreamInternal() { - if (isDreamingInternal() && !dreamHasFocus() && mController.bringDreamToFront()) { + if (isDreamingInternal() && !dreamIsFrontmost() && mController.bringDreamToFront()) { return; } @@ -1159,10 +1160,16 @@ public final class DreamManagerService extends SystemService { } @Override - public void onDreamFocusChanged(boolean hasFocus) { + public void setDreamIsObscured(boolean isObscured) { + if (!dreamHandlesBeingObscured()) { + return; + } + + checkPermission(android.Manifest.permission.WRITE_DREAM_STATE); + final long ident = Binder.clearCallingIdentity(); try { - mController.setDreamHasFocus(hasFocus); + mHandler.post(() -> mController.setDreamIsObscured(isObscured)); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/flags/pinner.aconfig b/services/core/java/com/android/server/flags/pinner.aconfig index 16a45cd87fd7..2f817dbb9a7f 100644 --- a/services/core/java/com/android/server/flags/pinner.aconfig +++ b/services/core/java/com/android/server/flags/pinner.aconfig @@ -6,4 +6,11 @@ flag { namespace: "system_performance" description: "This flag controls if webview should be pinned in memory." bug: "307594624" +} + +flag { + name: "skip_home_art_pins" + namespace: "system_performance" + description: "Ablation study flag that controls if home app odex/vdex files should be pinned in memory." + bug: "340935152" }
\ No newline at end of file diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index d21fc85d6448..5db17bb90637 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -29,7 +29,6 @@ import android.os.Binder; import android.os.Handler; import android.os.PowerManager; import android.os.SystemProperties; -import android.os.UserHandle; import android.sysprop.HdmiProperties; import android.util.Slog; @@ -278,8 +277,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { void dismissUiOnActiveSourceStatusRecovered() { assertRunOnServiceThread(); Intent intent = new Intent(HdmiControlManager.ACTION_ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI); - mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - HdmiControlService.PERMISSION); + mService.sendBroadcastAsUser(intent); } @Override diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 46061a56631c..275c9309ffc9 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -206,6 +206,10 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { launchDeviceDiscovery(); startQueuedActions(); if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { + if (hasAction(RequestActiveSourceAction.class)) { + Slog.i(TAG, "RequestActiveSourceAction is in progress. Restarting."); + removeAction(RequestActiveSourceAction.class); + } addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() { @Override public void onComplete(int result) { @@ -1308,6 +1312,8 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mService.sendCecCommand( HdmiCecMessageBuilder.buildActiveSource( getDeviceInfo().getLogicalAddress(), activePath)); + updateActiveSource(getDeviceInfo().getLogicalAddress(), activePath, + "HdmiCecLocalDeviceTv#launchRoutingControl()"); } } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index d10e19200eb2..cca73b5eabf0 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -40,6 +40,7 @@ import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -304,6 +305,10 @@ public class HdmiControlService extends SystemService { // Make sure HdmiCecConfig is instantiated and the XMLs are read. private HdmiCecConfig mHdmiCecConfig; + // Timeout value for start ARC action after an established eARC connection was terminated, + // e.g. because eARC was disabled in Settings. + private static final int EARC_TRIGGER_START_ARC_ACTION_DELAY = 500; + /** * Interface to report send result. */ @@ -1641,6 +1646,13 @@ public class HdmiControlService extends SystemService { case Constants.MESSAGE_ROUTING_CHANGE: case Constants.MESSAGE_SET_STREAM_PATH: case Constants.MESSAGE_TEXT_VIEW_ON: + // RequestActiveSourceAction is started after the TV finished logical address + // allocation. This action is used by the TV to get the active source from the CEC + // network. If the TV sent a source changing CEC message, this action does not have + // to continue anymore. + if (isTvDeviceEnabled()) { + tv().removeAction(RequestActiveSourceAction.class); + } sendCecCommandWithRetries(command, callback); break; default: @@ -3768,7 +3780,6 @@ public class HdmiControlService extends SystemService { return mWakeUpMessageReceived; } - @VisibleForTesting protected boolean isStandbyMessageReceived() { return mStandbyMessageReceived; } @@ -4389,8 +4400,7 @@ public class HdmiControlService extends SystemService { assertRunOnServiceThread(); Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); - getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - HdmiControlService.PERMISSION); + sendBroadcastAsUser(intent); } @ServiceThreadOnly @@ -4399,8 +4409,17 @@ public class HdmiControlService extends SystemService { Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra); - getContext().sendBroadcastAsUser(intent, UserHandle.ALL, - HdmiControlService.PERMISSION); + sendBroadcastAsUser(intent); + } + + // This method is used such that we can override it inside unit tests to avoid a + // SecurityException. + @ServiceThreadOnly + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + assertRunOnServiceThread(); + getContext().sendBroadcastAsUser(intent, UserHandle.ALL, HdmiControlService.PERMISSION); } @VisibleForTesting @@ -5042,7 +5061,12 @@ public class HdmiControlService extends SystemService { // AudioService here that the eARC connection is terminated. HdmiLogger.debug("eARC state change [new: HDMI_EARC_STATUS_ARC_PENDING(2)]"); notifyEarcStatusToAudioService(false, new ArrayList<>()); - startArcAction(true, null); + mHandler.postDelayed( new Runnable() { + @Override + public void run() { + startArcAction(true, null); + } + }, EARC_TRIGGER_START_ARC_ACTION_DELAY); getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(), oldEarcStatus, status, HdmiStatsEnums.LOG_REASON_EARC_STATUS_CHANGED); } else { diff --git a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java index d2504164a6df..539a00db45b8 100644 --- a/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java +++ b/services/core/java/com/android/server/hdmi/RequestActiveSourceAction.java @@ -21,13 +21,20 @@ import android.hardware.hdmi.IHdmiControlCallback; import android.util.Slog; /** - * Feature action that sends <Request Active Source> message and waits for <Active Source>. + * Feature action that sends <Request Active Source> message and waits for <Active Source> on TV + * panels. + * This action has a delay before sending <Request Active Source>. This is because it should wait + * for a possible request from LauncherX and can be cancelled if an <Active Source> message was + * received or the TV switched to another input. */ public class RequestActiveSourceAction extends HdmiCecFeatureAction { private static final String TAG = "RequestActiveSourceAction"; + // State to wait for the LauncherX to call the CEC API. + private static final int STATE_WAIT_FOR_LAUNCHERX_API_CALL = 1; + // State to wait for the <Active Source> message. - private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 1; + private static final int STATE_WAIT_FOR_ACTIVE_SOURCE = 2; // Number of retries <Request Active Source> is sent if no device answers this message. private static final int MAX_SEND_RETRY_COUNT = 1; @@ -43,10 +50,12 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction { boolean start() { Slog.v(TAG, "RequestActiveSourceAction started."); - sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); + mState = STATE_WAIT_FOR_LAUNCHERX_API_CALL; - mState = STATE_WAIT_FOR_ACTIVE_SOURCE; - addTimer(mState, HdmiConfig.TIMEOUT_MS); + // We wait for default timeout to allow the message triggered by the LauncherX API call to + // be sent by the TV and another default timeout in case the message has to be answered + // (e.g. TV sent a <Set Stream Path> or <Routing Change>). + addTimer(mState, HdmiConfig.TIMEOUT_MS * 2); return true; } @@ -65,13 +74,23 @@ public class RequestActiveSourceAction extends HdmiCecFeatureAction { if (mState != state) { return; } - if (mState == STATE_WAIT_FOR_ACTIVE_SOURCE) { - if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) { + + switch (mState) { + case STATE_WAIT_FOR_LAUNCHERX_API_CALL: + mState = STATE_WAIT_FOR_ACTIVE_SOURCE; sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); addTimer(mState, HdmiConfig.TIMEOUT_MS); - } else { - finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); - } + return; + case STATE_WAIT_FOR_ACTIVE_SOURCE: + if (mSendRetryCount++ < MAX_SEND_RETRY_COUNT) { + sendCommand(HdmiCecMessageBuilder.buildRequestActiveSource(getSourceAddress())); + addTimer(mState, HdmiConfig.TIMEOUT_MS); + } else { + finishWithCallback(HdmiControlManager.RESULT_TIMEOUT); + } + return; + default: + return; } } } 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/hdmi/SystemAudioInitiationActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java index a0801965d4b4..cd442c326979 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioInitiationActionFromAvr.java @@ -16,6 +16,7 @@ package com.android.server.hdmi; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -23,6 +24,7 @@ import com.android.internal.annotations.VisibleForTesting; * Feature action that handles System Audio Mode initiated by AVR devices. */ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { + private static final String TAG = "SystemAudioInitiationActionFromAvr"; // State that waits for <Active Source> once send <Request Active Source>. private static final int STATE_WAITING_FOR_ACTIVE_SOURCE = 1; @@ -115,6 +117,10 @@ public class SystemAudioInitiationActionFromAvr extends HdmiCecFeatureAction { private void handleActiveSourceTimeout() { HdmiLogger.debug("Cannot get active source."); + if (audioSystem().mService.isStandbyMessageReceived()) { + Slog.d(TAG, "Device is going to sleep, avoid to wake it up."); + return; + } // If not able to find Active Source and the current device has playbcak functionality, // claim Active Source and start to query TV system audio mode support. if (audioSystem().mService.isPlaybackDevice()) { diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java index 4e9cf51fda56..d32a5ed60094 100644 --- a/services/core/java/com/android/server/input/InputManagerInternal.java +++ b/services/core/java/com/android/server/input/InputManagerInternal.java @@ -84,29 +84,6 @@ public abstract class InputManagerInternal { @NonNull IBinder toChannelToken); /** - * Sets the display id that the MouseCursorController will be forced to target. Pass - * {@link android.view.Display#INVALID_DISPLAY} to clear the override. - * - * Note: This method generally blocks until the pointer display override has propagated. - * When setting a new override, the caller should ensure that an input device that can control - * the mouse pointer is connected. If a new override is set when no such input device is - * connected, the caller may be blocked for an arbitrary period of time. - * - * @return true if the pointer displayId was set successfully, or false if it fails. - * - * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete. - */ - public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId); - - /** - * Gets the display id that the MouseCursorController is being forced to target. Returns - * {@link android.view.Display#INVALID_DISPLAY} if there is no override - * - * @deprecated TODO(b/293587049): Not needed - remove after Pointer Icon Refactor is complete. - */ - public abstract int getVirtualMousePointerDisplayId(); - - /** * Gets the current position of the mouse cursor. * * Returns NaN-s as the coordinates if the cursor is not available. @@ -241,4 +218,13 @@ public abstract class InputManagerInternal { * display, external peripherals, fingerprint sensor, etc. */ public abstract void notifyUserActivity(); + + /** + * Get the device ID of the {@link InputDevice} that used most recently. + * + * @return the last used input device ID, or + * {@link android.os.IInputConstants#INVALID_INPUT_DEVICE_ID} if no device has been used + * since boot. + */ + public abstract int getLastUsedInputDeviceId(); } diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 39f31211d019..48cccd5b5b39 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 @@ -282,33 +281,9 @@ public class InputManagerService extends IInputManager.Stub // WARNING: Do not call other services outside of input while holding this lock. private final Object mAdditionalDisplayInputPropertiesLock = new Object(); - // Forces the PointerController to target a specific display id. - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY; - - // PointerController is the source of truth of the pointer display. This is the value of the - // latest pointer display id reported by PointerController. - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private int mAcknowledgedPointerDisplayId = Display.INVALID_DISPLAY; - // This is the latest display id that IMS has requested PointerController to use. If there are - // no devices that can control the pointer, PointerController may end up disregarding this - // value. - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private int mRequestedPointerDisplayId = Display.INVALID_DISPLAY; @GuardedBy("mAdditionalDisplayInputPropertiesLock") private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties = new SparseArray<>(); - // This contains the per-display properties that are currently applied by native code. It should - // be kept in sync with the properties for mRequestedPointerDisplayId. - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private final AdditionalDisplayInputProperties mCurrentDisplayProperties = - new AdditionalDisplayInputProperties(); - // TODO(b/293587049): Pointer Icon Refactor: There can be more than one pointer icon - // visible at once. Update this to support multi-pointer use cases. - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private int mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED; - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private PointerIcon mPointerIcon; // Holds all the registered gesture monitors that are implemented as spy windows. The spy // windows are mapped by their InputChannel tokens. @@ -330,6 +305,9 @@ public class InputManagerService extends IInputManager.Stub // Manages Sticky modifier state private final StickyModifierStateController mStickyModifierStateController; + // Manages Keyboard microphone mute led + private final KeyboardLedController mKeyboardLedController; + // Manages Keyboard modifier keys remapping private final KeyRemapper mKeyRemapper; @@ -479,6 +457,8 @@ public class InputManagerService extends IInputManager.Stub injector.getLooper(), injector.getUEventManager()) : new KeyboardBacklightControllerInterface() {}; mStickyModifierStateController = new StickyModifierStateController(); + mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(), + mNative); mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper()); mPointerIconCache = new PointerIconCache(mContext, mNative); @@ -593,6 +573,7 @@ public class InputManagerService extends IInputManager.Stub mKeyboardLayoutManager.systemRunning(); mBatteryController.systemRunning(); mKeyboardBacklightController.systemRunning(); + mKeyboardLedController.systemRunning(); mKeyRemapper.systemRunning(); mPointerIconCache.systemRunning(); } @@ -611,14 +592,9 @@ public class InputManagerService extends IInputManager.Stub } mNative.setDisplayViewports(vArray); - // Attempt to update the pointer display when viewports change when there is no override. + // Attempt to update the default pointer display when the viewports change. // Take care to not make calls to window manager while holding internal locks. - final int pointerDisplayId = mWindowManagerCallbacks.getPointerDisplayId(); - synchronized (mAdditionalDisplayInputPropertiesLock) { - if (mOverriddenPointerDisplayId == Display.INVALID_DISPLAY) { - updatePointerDisplayIdLocked(pointerDisplayId); - } - } + mNative.setPointerDisplayId(mWindowManagerCallbacks.getPointerDisplayId()); } /** @@ -984,12 +960,6 @@ public class InputManagerService extends IInputManager.Stub // Binder call @Override - public boolean isInputDeviceEnabled(int deviceId) { - return mNative.isInputDeviceEnabled(deviceId); - } - - // Binder call - @Override public void enableInputDevice(int deviceId) { if (!checkCallingPermission(android.Manifest.permission.DISABLE_INPUT_DEVICE, "enableInputDevice()")) { @@ -1347,84 +1317,6 @@ public class InputManagerService extends IInputManager.Stub properties -> properties.pointerIconVisible = visible); } - /** - * Update the display on which the mouse pointer is shown. - * - * @return true if the pointer displayId changed, false otherwise. - */ - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private boolean updatePointerDisplayIdLocked(int pointerDisplayId) { - if (mRequestedPointerDisplayId == pointerDisplayId) { - return false; - } - mRequestedPointerDisplayId = pointerDisplayId; - mNative.setPointerDisplayId(pointerDisplayId); - applyAdditionalDisplayInputProperties(); - return true; - } - - private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) { - synchronized (mAdditionalDisplayInputPropertiesLock) { - mAcknowledgedPointerDisplayId = args.mPointerDisplayId; - // Notify waiting threads that the display of the mouse pointer has changed. - mAdditionalDisplayInputPropertiesLock.notifyAll(); - } - mWindowManagerCallbacks.notifyPointerDisplayIdChanged( - args.mPointerDisplayId, args.mXPosition, args.mYPosition); - } - - private boolean setVirtualMousePointerDisplayIdBlocking(int overrideDisplayId) { - if (com.android.input.flags.Flags.enablePointerChoreographer()) { - throw new IllegalStateException( - "This must not be used when PointerChoreographer is enabled"); - } - final boolean isRemovingOverride = overrideDisplayId == Display.INVALID_DISPLAY; - - // Take care to not make calls to window manager while holding internal locks. - final int resolvedDisplayId = isRemovingOverride - ? mWindowManagerCallbacks.getPointerDisplayId() - : overrideDisplayId; - - synchronized (mAdditionalDisplayInputPropertiesLock) { - mOverriddenPointerDisplayId = overrideDisplayId; - - if (!updatePointerDisplayIdLocked(resolvedDisplayId) - && mAcknowledgedPointerDisplayId == resolvedDisplayId) { - // The requested pointer display is already set. - return true; - } - if (isRemovingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) { - // The pointer display override is being removed, but the current pointer display - // is already invalid. This can happen when the PointerController is destroyed as a - // result of the removal of all input devices that can control the pointer. - return true; - } - try { - // The pointer display changed, so wait until the change has propagated. - mAdditionalDisplayInputPropertiesLock.wait(5_000 /*mills*/); - } catch (InterruptedException ignored) { - } - // This request succeeds in two cases: - // - This request was to remove the override, in which case the new pointer display - // could be anything that WM has set. - // - We are setting a new override, in which case the request only succeeds if the - // reported new displayId is the one we requested. This check ensures that if two - // competing overrides are requested in succession, the caller can be notified if one - // of them fails. - return isRemovingOverride || mAcknowledgedPointerDisplayId == overrideDisplayId; - } - } - - private int getVirtualMousePointerDisplayId() { - if (com.android.input.flags.Flags.enablePointerChoreographer()) { - throw new IllegalStateException( - "This must not be used when PointerChoreographer is enabled"); - } - synchronized (mAdditionalDisplayInputPropertiesLock) { - return mOverriddenPointerDisplayId; - } - } - private void setDisplayEligibilityForPointerCapture(int displayId, boolean isEligible) { mNative.setDisplayEligibilityForPointerCapture(displayId, isEligible); } @@ -1708,45 +1600,10 @@ public class InputManagerService extends IInputManager.Stub // Binder call @Override - public void setPointerIconType(int iconType) { - if (iconType == PointerIcon.TYPE_CUSTOM) { - throw new IllegalArgumentException("Use setCustomPointerIcon to set custom pointers"); - } - synchronized (mAdditionalDisplayInputPropertiesLock) { - mPointerIcon = null; - mPointerIconType = iconType; - - if (!mCurrentDisplayProperties.pointerIconVisible) return; - - mNative.setPointerIconType(mPointerIconType); - } - } - - // Binder call - @Override - public void setCustomPointerIcon(PointerIcon icon) { - Objects.requireNonNull(icon); - synchronized (mAdditionalDisplayInputPropertiesLock) { - mPointerIconType = PointerIcon.TYPE_CUSTOM; - mPointerIcon = icon; - - if (!mCurrentDisplayProperties.pointerIconVisible) return; - - mNative.setCustomPointerIcon(mPointerIcon); - } - } - - // Binder call - @Override public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId, IBinder inputToken) { Objects.requireNonNull(icon); - synchronized (mAdditionalDisplayInputPropertiesLock) { - mPointerIconType = icon.getType(); - mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null; - - return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken); - } + return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken); } /** @@ -1793,7 +1650,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()")) { @@ -1804,13 +1662,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()")) { @@ -1819,7 +1677,7 @@ public class InputManagerService extends IInputManager.Stub Objects.requireNonNull(inputPort); synchronized (mAssociationsLock) { - mUniqueIdAssociations.remove(inputPort); + mUniqueIdAssociationsByPort.remove(inputPort); } mNative.changeUniqueIdAssociation(); } @@ -2218,6 +2076,7 @@ public class InputManagerService extends IInputManager.Stub dumpDisplayInputPropertiesValues(ipw); mBatteryController.dump(ipw); mKeyboardBacklightController.dump(ipw); + mKeyboardLedController.dump(ipw); } private void dumpAssociations(IndentingPrintWriter pw) { @@ -2237,9 +2096,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); }); @@ -2274,28 +2133,24 @@ public class InputManagerService extends IInputManager.Stub private void dumpDisplayInputPropertiesValues(IndentingPrintWriter pw) { synchronized (mAdditionalDisplayInputPropertiesLock) { - if (mAdditionalDisplayInputProperties.size() != 0) { - pw.println("mAdditionalDisplayInputProperties:"); - pw.increaseIndent(); + pw.println("mAdditionalDisplayInputProperties:"); + pw.increaseIndent(); + try { + if (mAdditionalDisplayInputProperties.size() == 0) { + pw.println("<none>"); + return; + } for (int i = 0; i < mAdditionalDisplayInputProperties.size(); i++) { - pw.println("displayId: " - + mAdditionalDisplayInputProperties.keyAt(i)); + pw.println("displayId: " + mAdditionalDisplayInputProperties.keyAt(i)); final AdditionalDisplayInputProperties properties = mAdditionalDisplayInputProperties.valueAt(i); pw.println("mousePointerAccelerationEnabled: " + properties.mousePointerAccelerationEnabled); pw.println("pointerIconVisible: " + properties.pointerIconVisible); } + } finally { pw.decreaseIndent(); } - if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { - pw.println("mOverriddenPointerDisplayId: " + mOverriddenPointerDisplayId); - } - - pw.println("mAcknowledgedPointerDisplayId=" + mAcknowledgedPointerDisplayId); - pw.println("mRequestedPointerDisplayId=" + mRequestedPointerDisplayId); - pw.println("mPointerIconType=" + PointerIcon.typeToString(mPointerIconType)); - pw.println("mPointerIcon=" + mPointerIcon); } } private boolean checkCallingPermission(String permission, String func) { @@ -2670,10 +2525,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); @@ -2810,26 +2665,6 @@ public class InputManagerService extends IInputManager.Stub return null; } - private static class PointerDisplayIdChangedArgs { - final int mPointerDisplayId; - final float mXPosition; - final float mYPosition; - PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) { - mPointerDisplayId = pointerDisplayId; - mXPosition = xPosition; - mYPosition = yPosition; - } - } - - // Native callback. - @SuppressWarnings("unused") - @VisibleForTesting - void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) { - mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED, - new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition, - yPosition)).sendToTarget(); - } - @Override @EnforcePermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE) public void registerStickyModifierStateListener( @@ -2982,14 +2817,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); } /** @@ -3033,9 +2860,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; } } } @@ -3260,17 +3084,6 @@ public class InputManagerService extends IInputManager.Stub } @Override - public boolean setVirtualMousePointerDisplayId(int pointerDisplayId) { - return InputManagerService.this - .setVirtualMousePointerDisplayIdBlocking(pointerDisplayId); - } - - @Override - public int getVirtualMousePointerDisplayId() { - return InputManagerService.this.getVirtualMousePointerDisplayId(); - } - - @Override public PointF getCursorPosition(int displayId) { final float[] p = mNative.getMouseCursorPosition(displayId); if (p == null || p.length != 2) { @@ -3367,6 +3180,11 @@ public class InputManagerService extends IInputManager.Stub public void setStylusButtonMotionEventsEnabled(boolean enabled) { mNative.setStylusButtonMotionEventsEnabled(enabled); } + + @Override + public int getLastUsedInputDeviceId() { + return mNative.getLastUsedInputDeviceId(); + } } @Override @@ -3406,44 +3224,6 @@ public class InputManagerService extends IInputManager.Stub } } - private void applyAdditionalDisplayInputProperties() { - synchronized (mAdditionalDisplayInputPropertiesLock) { - AdditionalDisplayInputProperties properties = - mAdditionalDisplayInputProperties.get(mRequestedPointerDisplayId); - if (properties == null) properties = DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES; - applyAdditionalDisplayInputPropertiesLocked(properties); - } - } - - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private void applyAdditionalDisplayInputPropertiesLocked( - AdditionalDisplayInputProperties properties) { - // Handle changes to each of the individual properties. - // TODO(b/293587049): This approach for updating pointer display properties is only for when - // PointerChoreographer is disabled. Remove this logic when PointerChoreographer is - // permanently enabled. - - if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) { - mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible; - if (properties.pointerIconVisible) { - if (mPointerIconType == PointerIcon.TYPE_CUSTOM) { - Objects.requireNonNull(mPointerIcon); - mNative.setCustomPointerIcon(mPointerIcon); - } else { - mNative.setPointerIconType(mPointerIconType); - } - } else { - mNative.setPointerIconType(PointerIcon.TYPE_NULL); - } - } - - if (properties.mousePointerAccelerationEnabled - != mCurrentDisplayProperties.mousePointerAccelerationEnabled) { - mCurrentDisplayProperties.mousePointerAccelerationEnabled = - properties.mousePointerAccelerationEnabled; - } - } - private void updateAdditionalDisplayInputProperties(int displayId, Consumer<AdditionalDisplayInputProperties> updater) { synchronized (mAdditionalDisplayInputPropertiesLock) { @@ -3466,13 +3246,6 @@ public class InputManagerService extends IInputManager.Stub if (properties.allDefaults()) { mAdditionalDisplayInputProperties.remove(displayId); } - if (displayId != mRequestedPointerDisplayId) { - Log.i(TAG, "Not applying additional properties for display " + displayId - + " because the pointer is currently targeting display " - + mRequestedPointerDisplayId + "."); - return; - } - applyAdditionalDisplayInputPropertiesLocked(properties); } } diff --git a/services/core/java/com/android/server/input/KeyboardLedController.java b/services/core/java/com/android/server/input/KeyboardLedController.java new file mode 100644 index 000000000000..5c404a2ae6e7 --- /dev/null +++ b/services/core/java/com/android/server/input/KeyboardLedController.java @@ -0,0 +1,171 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.input; + +import android.annotation.NonNull; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Color; +import android.hardware.SensorPrivacyManager; +import android.hardware.SensorPrivacyManager.Sensors; +import android.hardware.input.InputManager; +import android.hardware.lights.Light; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.UserHandle; +import android.util.IndentingPrintWriter; +import android.util.SparseArray; +import android.view.InputDevice; + +import java.io.PrintWriter; +import java.util.Objects; + +/** + * This class is used to control the light of keyboard. + */ +public final class KeyboardLedController implements InputManager.InputDeviceListener { + + private static final String TAG = KeyboardLedController.class.getSimpleName(); + private static final int MSG_UPDATE_EXISTING_DEVICES = 1; + private static final int MSG_UPDATE_MIC_MUTE_LED_STATE = 2; + + private final Context mContext; + private final Handler mHandler; + private final NativeInputManagerService mNative; + private final SparseArray<InputDevice> mKeyboardsWithMicMuteLed = new SparseArray<>(); + @NonNull + private InputManager mInputManager; + @NonNull + private SensorPrivacyManager mSensorPrivacyManager; + @NonNull + private AudioManager mAudioManager; + private BroadcastReceiver mMicrophoneMuteChangedIntentReceiver = + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Message msg = Message.obtain(mHandler, MSG_UPDATE_MIC_MUTE_LED_STATE); + mHandler.sendMessage(msg); + } + }; + + KeyboardLedController(Context context, Looper looper, + NativeInputManagerService nativeService) { + mContext = context; + mNative = nativeService; + mHandler = new Handler(looper, this::handleMessage); + } + + private boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_EXISTING_DEVICES: + for (int deviceId : (int[]) msg.obj) { + onInputDeviceAdded(deviceId); + } + return true; + case MSG_UPDATE_MIC_MUTE_LED_STATE: + updateMicMuteLedState(); + return true; + } + return false; + } + + private void updateMicMuteLedState() { + // We determine if the microphone is muted by querying both the hardware state of the + // microphone and the microphone sensor privacy hardware and sensor toggles + boolean isMicrophoneMute = mAudioManager.isMicrophoneMute() + || mSensorPrivacyManager.areAnySensorPrivacyTogglesEnabled(Sensors.MICROPHONE); + int color = isMicrophoneMute ? Color.WHITE : Color.TRANSPARENT; + for (int i = 0; i < mKeyboardsWithMicMuteLed.size(); i++) { + InputDevice device = mKeyboardsWithMicMuteLed.valueAt(i); + if (device != null) { + int deviceId = device.getId(); + Light light = getKeyboardMicMuteLight(device); + if (light != null) { + mNative.setLightColor(deviceId, light.getId(), color); + } + } + } + } + + private Light getKeyboardMicMuteLight(InputDevice device) { + for (Light light : device.getLightsManager().getLights()) { + if (light.getType() == Light.LIGHT_TYPE_KEYBOARD_MIC_MUTE + && light.hasBrightnessControl()) { + return light; + } + } + return null; + } + + /** Called when the system is ready for us to start third-party code. */ + public void systemRunning() { + mSensorPrivacyManager = Objects.requireNonNull( + mContext.getSystemService(SensorPrivacyManager.class)); + mInputManager = Objects.requireNonNull(mContext.getSystemService(InputManager.class)); + mAudioManager = Objects.requireNonNull(mContext.getSystemService(AudioManager.class)); + mInputManager.registerInputDeviceListener(this, mHandler); + Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES, + mInputManager.getInputDeviceIds()); + mHandler.sendMessage(msg); + mContext.registerReceiverAsUser( + mMicrophoneMuteChangedIntentReceiver, + UserHandle.ALL, + new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED), + null, + mHandler); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + onInputDeviceChanged(deviceId); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + mKeyboardsWithMicMuteLed.remove(deviceId); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + InputDevice inputDevice = mInputManager.getInputDevice(deviceId); + if (inputDevice == null) { + return; + } + if (getKeyboardMicMuteLight(inputDevice) != null) { + mKeyboardsWithMicMuteLed.put(deviceId, inputDevice); + Message msg = Message.obtain(mHandler, MSG_UPDATE_MIC_MUTE_LED_STATE); + mHandler.sendMessage(msg); + } + } + + /** Dump the diagnostic information */ + public void dump(PrintWriter pw) { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw); + ipw.println(TAG + ": " + mKeyboardsWithMicMuteLed.size() + " keyboard mic mute lights"); + ipw.increaseIndent(); + for (int i = 0; i < mKeyboardsWithMicMuteLed.size(); i++) { + InputDevice inputDevice = mKeyboardsWithMicMuteLed.valueAt(i); + ipw.println(i + " " + inputDevice.getName() + ": " + + getKeyboardMicMuteLight(inputDevice).toString()); + } + ipw.decreaseIndent(); + } +} diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 32d5044d9e41..a9d40bb54f96 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -183,18 +183,12 @@ interface NativeInputManagerService { void monitor(); - boolean isInputDeviceEnabled(int deviceId); - void enableInputDevice(int deviceId); void disableInputDevice(int deviceId); - void setPointerIconType(int iconId); - void reloadPointerIcons(); - void setCustomPointerIcon(@NonNull PointerIcon icon); - boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId, int pointerId, @NonNull IBinder inputToken); @@ -275,6 +269,15 @@ interface NativeInputManagerService { void setInputMethodConnectionIsActive(boolean isActive); + /** + * Get the device ID of the InputDevice that used most recently. + * + * @return the last used input device ID, or + * {@link android.os.IInputConstants#INVALID_INPUT_DEVICE_ID} if no device has been used + * since boot. + */ + int getLastUsedInputDeviceId(); + /** The native implementation of InputManagerService methods. */ class NativeImpl implements NativeInputManagerService { /** Pointer to native input manager service object, used by native code. */ @@ -458,24 +461,15 @@ interface NativeInputManagerService { public native void monitor(); @Override - public native boolean isInputDeviceEnabled(int deviceId); - - @Override public native void enableInputDevice(int deviceId); @Override public native void disableInputDevice(int deviceId); @Override - public native void setPointerIconType(int iconId); - - @Override public native void reloadPointerIcons(); @Override - public native void setCustomPointerIcon(PointerIcon icon); - - @Override public native boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId, IBinder inputToken); @@ -554,5 +548,8 @@ interface NativeInputManagerService { @Override public native void setInputMethodConnectionIsActive(boolean isActive); + + @Override + public native int getLastUsedInputDeviceId(); } } 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..0749edce97a1 100644 --- a/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java +++ b/services/core/java/com/android/server/inputmethod/AutofillSuggestionsController.java @@ -18,7 +18,6 @@ package com.android.server.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.UserIdInt; import android.os.IBinder; import android.os.RemoteException; import android.util.Slog; @@ -29,6 +28,7 @@ import android.view.inputmethod.InputMethodInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInlineSuggestionsResponseCallback; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; /** @@ -38,16 +38,23 @@ final class AutofillSuggestionsController { private static final boolean DEBUG = false; private static final String TAG = AutofillSuggestionsController.class.getSimpleName(); - @NonNull private final InputMethodManagerService mService; + @NonNull private final InputMethodBindingController mBindingController; + + /** + * 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; + @NonNull final InlineSuggestionsRequestCallback mCallback; @NonNull final String mPackageName; CreateInlineSuggestionsRequest( @NonNull InlineSuggestionsRequestInfo requestInfo, - @NonNull IInlineSuggestionsRequestCallback callback, + @NonNull InlineSuggestionsRequestCallback callback, @NonNull String packageName) { mRequestInfo = requestInfo; mCallback = callback; @@ -71,41 +78,50 @@ final class AutofillSuggestionsController { */ @GuardedBy("ImfLock.class") @Nullable - private IInlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback; + private InlineSuggestionsRequestCallback mInlineSuggestionsRequestCallback; - AutofillSuggestionsController(@NonNull InputMethodManagerService service) { - mService = service; + AutofillSuggestionsController(@NonNull InputMethodBindingController bindingController) { + mBindingController = bindingController; } @GuardedBy("ImfLock.class") - void onCreateInlineSuggestionsRequest(@UserIdInt int userId, - InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback, - boolean touchExplorationEnabled) { + void onResetSystemUi() { + mCurHostInputToken = null; + } + + @Nullable + @GuardedBy("ImfLock.class") + IBinder getCurHostInputToken() { + return mCurHostInputToken; + } + + @GuardedBy("ImfLock.class") + void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, + InlineSuggestionsRequestCallback callback, boolean touchExplorationEnabled) { clearPendingInlineSuggestionsRequest(); mInlineSuggestionsRequestCallback = callback; - final InputMethodInfo imi = mService.queryInputMethodForCurrentUserLocked( - mService.getSelectedMethodIdLocked()); - try { - if (userId == mService.getCurrentImeUserIdLocked() - && imi != null && isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) { - mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest( - requestInfo, callback, imi.getPackageName()); - if (mService.getCurMethodLocked() != null) { - // In the normal case when the IME is connected, we can make the request here. - performOnCreateInlineSuggestionsRequest(); - } else { - // Otherwise, the next time the IME connection is established, - // InputMethodBindingController.mMainConnection#onServiceConnected() will call - // into #performOnCreateInlineSuggestionsRequestLocked() to make the request. - if (DEBUG) { - Slog.d(TAG, "IME not connected. Delaying inline suggestions request."); - } - } - } else { - callback.onInlineSuggestionsUnsupported(); + + // Note that current user ID is guaranteed to be userId. + final var imeId = mBindingController.getSelectedMethodId(); + final InputMethodInfo imi = InputMethodSettingsRepository.get(mBindingController.mUserId) + .getMethodMap().get(imeId); + if (imi == null || !isInlineSuggestionsEnabled(imi, touchExplorationEnabled)) { + callback.onInlineSuggestionsUnsupported(); + return; + } + + mPendingInlineSuggestionsRequest = new CreateInlineSuggestionsRequest( + requestInfo, callback, imi.getPackageName()); + if (mBindingController.getCurMethod() != null) { + // In the normal case when the IME is connected, we can make the request here. + performOnCreateInlineSuggestionsRequest(); + } else { + // Otherwise, the next time the IME connection is established, + // InputMethodBindingController.mMainConnection#onServiceConnected() will call + // into #performOnCreateInlineSuggestionsRequestLocked() to make the request. + if (DEBUG) { + Slog.d(TAG, "IME not connected. Delaying inline suggestions request."); } - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e); } } @@ -114,7 +130,7 @@ final class AutofillSuggestionsController { if (mPendingInlineSuggestionsRequest == null) { return; } - IInputMethodInvoker curMethod = mService.getCurMethodLocked(); + IInputMethodInvoker curMethod = mBindingController.getCurMethod(); if (DEBUG) { Slog.d(TAG, "Performing onCreateInlineSuggestionsRequest. mCurMethod = " + curMethod); } @@ -123,9 +139,8 @@ final class AutofillSuggestionsController { new InlineSuggestionsRequestCallbackDecorator( mPendingInlineSuggestionsRequest.mCallback, mPendingInlineSuggestionsRequest.mPackageName, - mService.getCurTokenDisplayIdLocked(), - mService.getCurTokenLocked(), - mService); + mBindingController.getCurTokenDisplayId(), + mBindingController.getCurToken()); curMethod.onCreateInlineSuggestionsRequest( mPendingInlineSuggestionsRequest.mRequestInfo, callback); } else { @@ -149,11 +164,7 @@ final class AutofillSuggestionsController { @GuardedBy("ImfLock.class") void invalidateAutofillSession() { if (mInlineSuggestionsRequestCallback != null) { - try { - mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated(); - } catch (RemoteException e) { - Slog.e(TAG, "Cannot invalidate autofill session.", e); - } + mInlineSuggestionsRequestCallback.onInlineSuggestionsSessionInvalidated(); } } @@ -161,26 +172,24 @@ 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 InlineSuggestionsRequestCallback 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) { + @NonNull InlineSuggestionsRequestCallback callback, @NonNull String imePackageName, + int displayId, @NonNull IBinder imeToken) { mCallback = callback; mImePackageName = imePackageName; mImeDisplayId = displayId; mImeToken = imeToken; - mImms = imms; } @Override - public void onInlineSuggestionsUnsupported() throws RemoteException { + public void onInlineSuggestionsUnsupported() { mCallback.onInlineSuggestionsUnsupported(); } @@ -195,37 +204,42 @@ final class AutofillSuggestionsController { + "]."); } request.setHostDisplayId(mImeDisplayId); - mImms.setCurHostInputToken(mImeToken, request.getHostInputToken()); + synchronized (ImfLock.class) { + final IBinder curImeToken = mBindingController.getCurToken(); + if (mImeToken == curImeToken) { + mCurHostInputToken = request.getHostInputToken(); + } + } mCallback.onInlineSuggestionsRequest(request, callback); } @Override - public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { + public void onInputMethodStartInput(AutofillId imeFieldId) { mCallback.onInputMethodStartInput(imeFieldId); } @Override - public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { + public void onInputMethodShowInputRequested(boolean requestResult) { mCallback.onInputMethodShowInputRequested(requestResult); } @Override - public void onInputMethodStartInputView() throws RemoteException { + public void onInputMethodStartInputView() { mCallback.onInputMethodStartInputView(); } @Override - public void onInputMethodFinishInputView() throws RemoteException { + public void onInputMethodFinishInputView() { mCallback.onInputMethodFinishInputView(); } @Override - public void onInputMethodFinishInput() throws RemoteException { + public void onInputMethodFinishInput() { mCallback.onInputMethodFinishInput(); } @Override - public void onInlineSuggestionsSessionInvalidated() throws RemoteException { + public void onInlineSuggestionsSessionInvalidated() { mCallback.onInlineSuggestionsSessionInvalidated(); } } 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/ImeBindingState.java b/services/core/java/com/android/server/inputmethod/ImeBindingState.java index 4c20c3b9784a..f78ea84efb77 100644 --- a/services/core/java/com/android/server/inputmethod/ImeBindingState.java +++ b/services/core/java/com/android/server/inputmethod/ImeBindingState.java @@ -21,7 +21,9 @@ import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCU import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.os.IBinder; +import android.os.UserHandle; import android.util.Printer; import android.util.proto.ProtoOutputStream; import android.view.WindowManager; @@ -36,6 +38,9 @@ import com.android.server.wm.WindowManagerInternal; */ final class ImeBindingState { + @UserIdInt + final int mUserId; + /** * The last window token that we confirmed to be focused. This is always updated upon * reports from the input method client. If the window state is already changed before the @@ -90,6 +95,7 @@ final class ImeBindingState { static ImeBindingState newEmptyState() { return new ImeBindingState( + /*userId=*/ UserHandle.USER_NULL, /*focusedWindow=*/ null, /*focusedWindowSoftInputMode=*/ SOFT_INPUT_STATE_UNSPECIFIED, /*focusedWindowClient=*/ null, @@ -97,10 +103,12 @@ final class ImeBindingState { ); } - ImeBindingState(@Nullable IBinder focusedWindow, + ImeBindingState(@UserIdInt int userId, + @Nullable IBinder focusedWindow, @SoftInputModeFlags int focusedWindowSoftInputMode, @Nullable ClientState focusedWindowClient, @Nullable EditorInfo focusedWindowEditorInfo) { + mUserId = userId; mFocusedWindow = focusedWindow; mFocusedWindowSoftInputMode = focusedWindowSoftInputMode; mFocusedWindowClient = focusedWindowClient; diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index 1c14fc1b08dd..fff0e6e1b995 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -133,6 +133,13 @@ public final class ImeTrackerService extends IImeTracker.Stub { } } + @Override + public void onDispatched(@NonNull ImeTracker.Token statsToken) { + synchronized (mLock) { + mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); + } + } + /** * Updates the IME request tracking token with new information available in IMMS. * diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 3e23f972bd45..8191ee14adff 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -21,6 +21,7 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.app.ActivityOptions; import android.app.PendingIntent; import android.content.ComponentName; @@ -38,6 +39,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.EventLog; import android.util.Slog; +import android.view.Display; import android.view.WindowManager; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; @@ -46,6 +48,8 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.IInputMethod; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; +import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.UnbindReason; import com.android.server.EventLogTags; @@ -63,8 +67,10 @@ final class InputMethodBindingController { /** Time in milliseconds that the IME service has to bind before it is reconnected. */ static final long TIME_TO_RECONNECT = 3 * 1000; + @UserIdInt final int mUserId; @NonNull private final InputMethodManagerService mService; @NonNull private final Context mContext; + @NonNull private final AutofillSuggestionsController mAutofillController; @NonNull private final PackageManagerInternal mPackageManagerInternal; @NonNull private final WindowManagerInternal mWindowManagerInternal; @@ -76,6 +82,7 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod; @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID; @GuardedBy("ImfLock.class") @Nullable private IBinder mCurToken; + @GuardedBy("ImfLock.class") private int mCurTokenDisplayId = Display.INVALID_DISPLAY; @GuardedBy("ImfLock.class") private int mCurSeq; @GuardedBy("ImfLock.class") private boolean mVisibleBound; @GuardedBy("ImfLock.class") private boolean mSupportsStylusHw; @@ -107,14 +114,18 @@ final class InputMethodBindingController { | Context.BIND_INCLUDE_CAPABILITIES | Context.BIND_SHOWING_UI; - InputMethodBindingController(@NonNull InputMethodManagerService service) { - this(service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */); + InputMethodBindingController(@UserIdInt int userId, + @NonNull InputMethodManagerService service) { + this(userId, service, IME_CONNECTION_BIND_FLAGS, null /* latchForTesting */); } - InputMethodBindingController(@NonNull InputMethodManagerService service, - int imeConnectionBindFlags, CountDownLatch latchForTesting) { + InputMethodBindingController(@UserIdInt int userId, + @NonNull InputMethodManagerService service, int imeConnectionBindFlags, + CountDownLatch latchForTesting) { + mUserId = userId; mService = service; mContext = mService.mContext; + mAutofillController = new AutofillSuggestionsController(this); mPackageManagerInternal = mService.mPackageManagerInternal; mWindowManagerInternal = mService.mWindowManagerInternal; mImeConnectionBindFlags = imeConnectionBindFlags; @@ -188,6 +199,17 @@ final class InputMethodBindingController { } /** + * Returns the displayId associated with {@link #getCurToken()}. + * + * @return the displayId associated with {@link #getCurToken()}. {@link Display#INVALID_DISPLAY} + * while {@link #getCurToken()} returns {@code null} + */ + @GuardedBy("ImfLock.class") + int getCurTokenDisplayId() { + return mCurTokenDisplayId; + } + + /** * The Intent used to connect to the current input method. */ @GuardedBy("ImfLock.class") @@ -264,7 +286,7 @@ final class InputMethodBindingController { private final ServiceConnection mVisibleConnection = new ServiceConnection() { @Override public void onBindingDied(ComponentName name) { synchronized (ImfLock.class) { - mService.invalidateAutofillSessionLocked(); + mAutofillController.invalidateAutofillSession(); if (isVisibleBound()) { unbindVisibleConnection(); } @@ -276,7 +298,7 @@ final class InputMethodBindingController { @Override public void onServiceDisconnected(ComponentName name) { synchronized (ImfLock.class) { - mService.invalidateAutofillSessionLocked(); + mAutofillController.invalidateAutofillSession(); } } }; @@ -301,7 +323,8 @@ final class InputMethodBindingController { } if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); final InputMethodInfo info = - mService.queryInputMethodForCurrentUserLocked(mSelectedMethodId); + InputMethodSettingsRepository.get(mUserId).getMethodMap().get( + mSelectedMethodId); boolean supportsStylusHwChanged = mSupportsStylusHw != info.supportsStylusHandwriting(); mSupportsStylusHw = info.supportsStylusHandwriting(); @@ -320,7 +343,7 @@ final class InputMethodBindingController { mService.initializeImeLocked(mCurMethod, mCurToken); mService.scheduleNotifyImeUidToAudioService(mCurMethodUid); mService.reRequestCurrentClientSessionLocked(); - mService.performOnCreateInlineSuggestionsRequestLocked(); + mAutofillController.performOnCreateInlineSuggestionsRequest(); } // reset Handwriting event receiver. @@ -339,7 +362,7 @@ final class InputMethodBindingController { private void updateCurrentMethodUid() { final String curMethodPackage = mCurIntent.getComponent().getPackageName(); final int curMethodUid = mPackageManagerInternal.getPackageUid( - curMethodPackage, 0 /* flags */, mService.getCurrentImeUserIdLocked()); + curMethodPackage, 0 /* flags */, mUserId); if (curMethodUid < 0) { Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage); mCurMethodUid = Process.INVALID_UID; @@ -379,6 +402,24 @@ final class InputMethodBindingController { }; @GuardedBy("ImfLock.class") + void invalidateAutofillSession() { + mAutofillController.invalidateAutofillSession(); + } + + @GuardedBy("ImfLock.class") + void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo, + InlineSuggestionsRequestCallback callback, boolean touchExplorationEnabled) { + mAutofillController.onCreateInlineSuggestionsRequest(requestInfo, callback, + touchExplorationEnabled); + } + + @GuardedBy("ImfLock.class") + @Nullable + IBinder getCurHostInputToken() { + return mAutofillController.getCurHostInputToken(); + } + + @GuardedBy("ImfLock.class") void unbindCurrentMethod() { if (isVisibleBound()) { unbindVisibleConnection(); @@ -391,6 +432,7 @@ final class InputMethodBindingController { if (getCurToken() != null) { removeCurrentToken(); mService.resetSystemUiLocked(); + mAutofillController.onResetSystemUi(); } mCurId = null; @@ -406,15 +448,14 @@ final class InputMethodBindingController { @GuardedBy("ImfLock.class") private void removeCurrentToken() { - int curTokenDisplayId = mService.getCurTokenDisplayIdLocked(); - if (DEBUG) { Slog.v(TAG, - "Removing window token: " + mCurToken + " for display: " + curTokenDisplayId); + "Removing window token: " + mCurToken + " for display: " + mCurTokenDisplayId); } mWindowManagerInternal.removeWindowToken(mCurToken, true /* removeWindows */, - false /* animateExit */, curTokenDisplayId); + false /* animateExit */, mCurTokenDisplayId); mCurToken = null; + mCurTokenDisplayId = Display.INVALID_DISPLAY; } @GuardedBy("ImfLock.class") @@ -425,7 +466,8 @@ final class InputMethodBindingController { return InputBindResult.NO_IME; } - InputMethodInfo info = mService.queryInputMethodForCurrentUserLocked(mSelectedMethodId); + InputMethodInfo info = InputMethodSettingsRepository.get(mUserId).getMethodMap().get( + mSelectedMethodId); if (info == null) { throw new IllegalArgumentException("Unknown id: " + mSelectedMethodId); } @@ -436,7 +478,16 @@ final class InputMethodBindingController { mCurId = info.getId(); mLastBindTime = SystemClock.uptimeMillis(); - addFreshWindowToken(); + final int displayIdToShowIme = mService.getDisplayIdToShowImeLocked(); + mCurToken = new Binder(); + mCurTokenDisplayId = displayIdToShowIme; + if (DEBUG) { + Slog.v(TAG, "Adding window token: " + mCurToken + " for display: " + + displayIdToShowIme); + } + mWindowManagerInternal.addWindowToken(mCurToken, + WindowManager.LayoutParams.TYPE_INPUT_METHOD, + displayIdToShowIme, null /* options */); return new InputBindResult( InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, null, null, null, mCurId, mCurSeq, false); @@ -464,22 +515,6 @@ final class InputMethodBindingController { } @GuardedBy("ImfLock.class") - private void addFreshWindowToken() { - int displayIdToShowIme = mService.getDisplayIdToShowImeLocked(); - mCurToken = new Binder(); - - mService.setCurTokenDisplayIdLocked(displayIdToShowIme); - - if (DEBUG) { - Slog.v(TAG, "Adding window token: " + mCurToken + " for display: " - + displayIdToShowIme); - } - mWindowManagerInternal.addWindowToken(mCurToken, - WindowManager.LayoutParams.TYPE_INPUT_METHOD, - displayIdToShowIme, null /* options */); - } - - @GuardedBy("ImfLock.class") private void unbindMainConnection() { mContext.unbindService(mMainConnection); mHasMainConnection = false; @@ -497,8 +532,7 @@ final class InputMethodBindingController { Slog.e(TAG, "--- bind failed: service = " + mCurIntent + ", conn = " + conn); return false; } - return mContext.bindServiceAsUser(mCurIntent, conn, flags, - new UserHandle(mService.getCurrentImeUserIdLocked())); + return mContext.bindServiceAsUser(mCurIntent, conn, flags, new UserHandle(mUserId)); } @GuardedBy("ImfLock.class") diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java index 6339686629f5..458c3598d7ae 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java @@ -22,6 +22,7 @@ import static com.android.server.inputmethod.SubtypeUtils.SUBTYPE_MODE_KEYBOARD; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; +import android.os.Parcel; import android.text.TextUtils; import android.util.Slog; import android.view.inputmethod.InputMethodInfo; @@ -323,4 +324,24 @@ final class InputMethodInfoUtils { return SubtypeUtils.containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode); } + + /** + * Marshals the given {@link InputMethodInfo} into a byte array. + * + * @param imi {@link InputMethodInfo} to be marshalled + * @return a byte array where the given {@link InputMethodInfo} is marshalled + */ + @NonNull + static byte[] marshal(@NonNull InputMethodInfo imi) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + parcel.writeTypedObject(imi, 0); + return parcel.marshall(); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 1d048cb687cb..dace67f2c462 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -25,7 +25,7 @@ import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InputMethodInfo; import com.android.internal.inputmethod.IAccessibilityInputMethodSession; -import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.server.LocalServices; @@ -86,11 +86,11 @@ public abstract class InputMethodManagerInternal { * * @param userId the user ID to be queried * @param requestInfo information needed to create an {@link InlineSuggestionsRequest}. - * @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request + * @param cb {@link InlineSuggestionsRequestCallback} used to pass back the request * object */ public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId, - InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb); + InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb); /** * Force switch to the enabled input method by {@code imeId} for current user. If the input @@ -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 @@ -262,7 +263,7 @@ public abstract class InputMethodManagerInternal { @Override public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, InlineSuggestionsRequestInfo requestInfo, - IInlineSuggestionsRequestCallback cb) { + InlineSuggestionsRequestCallback cb) { } @Override @@ -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 8985022ea1b8..88474deb203d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -147,7 +147,6 @@ import com.android.internal.inputmethod.IAccessibilityInputMethodSession; import com.android.internal.inputmethod.IBooleanListener; import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; import com.android.internal.inputmethod.IImeTracker; -import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.IInputMethodClient; @@ -157,6 +156,7 @@ import com.android.internal.inputmethod.IInputMethodSessionCallback; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.ImeTracing; +import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.InputMethodDebug; @@ -205,6 +205,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.IntConsumer; +import java.util.function.IntFunction; /** * This class provides a system service that manages input methods. @@ -261,6 +262,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; private static final String HANDLER_THREAD_NAME = "android.imms"; + private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2"; /** * When set, {@link #startInputUncheckedLocked} will return @@ -280,10 +282,35 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @NonNull private final String[] mNonPreemptibleInputMethods; + /** + * See {@link #shouldEnableExperimentalConcurrentMultiUserMode(Context)} about when set to be + * {@code true}. + */ + private final boolean mExperimentalConcurrentMultiUserModeEnabled; + + /** + * Returns {@code true} if experimental concurrent multi-user mode is enabled. + * + * <p>Currently not compatible with profiles (e.g. work profile).</p> + * + * @param context {@link Context} to be used to query + * {@link PackageManager#FEATURE_AUTOMOTIVE} + * @return {@code true} if experimental concurrent multi-user mode is enabled. + */ + static boolean shouldEnableExperimentalConcurrentMultiUserMode(@NonNull Context context) { + return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + && UserManager.isVisibleBackgroundUsersEnabled() + && context.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled) + && Flags.concurrentInputMethods(); + } + final Context mContext; final Resources mRes; private final Handler mHandler; + @NonNull + private final Handler mPackageMonitorHandler; + @MultiUserUnawareField @UserIdInt @GuardedBy("ImfLock.class") @@ -305,17 +332,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final UserManagerInternal mUserManagerInternal; @MultiUserUnawareField private final InputMethodMenuController mMenuController; - @MultiUserUnawareField - @NonNull private final InputMethodBindingController mBindingController; - @MultiUserUnawareField - @NonNull private final AutofillSuggestionsController mAutofillController; @GuardedBy("ImfLock.class") @MultiUserUnawareField - @NonNull private final ImeVisibilityStateComputer mVisibilityStateComputer; + @NonNull + private final ImeVisibilityStateComputer mVisibilityStateComputer; @GuardedBy("ImfLock.class") - @NonNull private final DefaultImeVisibilityApplier mVisibilityApplier; + @NonNull + private final DefaultImeVisibilityApplier mVisibilityApplier; /** * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}. @@ -364,7 +389,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @MultiUserUnawareField private int mDeviceIdToShowIme = DEVICE_ID_DEFAULT; - @Nullable private StatusBarManagerInternal mStatusBarManagerInternal; + @Nullable + private StatusBarManagerInternal mStatusBarManagerInternal; private boolean mShowOngoingImeSwitcherForPhones; @GuardedBy("ImfLock.class") @MultiUserUnawareField @@ -462,6 +488,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. */ boolean mSystemReady; + @GuardedBy("ImfLock.class") + @NonNull + InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) { + return mUserDataRepository.getOrCreate(userId).mBindingController; + } + /** * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method. * This is to be synchronized with the secure settings keyed with @@ -478,25 +510,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable String getSelectedMethodIdLocked() { - return mBindingController.getSelectedMethodId(); - } - - /** - * The current binding sequence number, incremented every time there is - * a new bind performed. - */ - @GuardedBy("ImfLock.class") - private int getSequenceNumberLocked() { - return mBindingController.getSequenceNumber(); - } - - /** - * Increase the current binding sequence number by one. - * Reset to 1 on overflow. - */ - @GuardedBy("ImfLock.class") - private void advanceSequenceNumberLocked() { - mBindingController.advanceSequenceNumber(); + return getInputMethodBindingController(mCurrentUserId).getSelectedMethodId(); } @GuardedBy("ImfLock.class") @@ -536,7 +550,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * The {@link IRemoteAccessibilityInputConnection} last provided by the current client. */ @MultiUserUnawareField - @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection; + @Nullable + IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection; /** * The {@link EditorInfo} last provided by the current client. @@ -546,20 +561,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. EditorInfo mCurEditorInfo; /** - * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently - * connected to or in the process of connecting to. - * - * <p>This can be {@code null} when no input method is connected.</p> - * - * @see #getSelectedMethodIdLocked() - */ - @GuardedBy("ImfLock.class") - @Nullable - private String getCurIdLocked() { - return mBindingController.getCurId(); - } - - /** * The current subtype of the current input method. */ @MultiUserUnawareField @@ -575,15 +576,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() { - return mBindingController.hasMainConnection(); - } - - /** * The token tracking the current IME show request that is waiting for a connection to an IME, * otherwise {@code null}. */ @@ -598,22 +590,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. boolean mInFullscreenMode; /** - * The Intent used to connect to the current input method. - */ - @GuardedBy("ImfLock.class") - @Nullable - private Intent getCurIntentLocked() { - return mBindingController.getCurIntent(); - } - - /** * The token we have made for the currently active input method, to * identify it in the future. */ @GuardedBy("ImfLock.class") @Nullable IBinder getCurTokenLocked() { - return mBindingController.getCurToken(); + return getInputMethodBindingController(mCurrentUserId).getCurToken(); } /** @@ -621,26 +604,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. */ @GuardedBy("ImfLock.class") int getCurTokenDisplayIdLocked() { - return mCurTokenDisplayId; - } - - @GuardedBy("ImfLock.class") - void setCurTokenDisplayIdLocked(int curTokenDisplayId) { - mCurTokenDisplayId = curTokenDisplayId; + return getInputMethodBindingController(mCurrentUserId).getCurTokenDisplayId(); } - @GuardedBy("ImfLock.class") - @MultiUserUnawareField - 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}. @@ -654,24 +620,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable IInputMethodInvoker getCurMethodLocked() { - return mBindingController.getCurMethod(); - } - - /** - * If not {@link Process#INVALID_UID}, then the UID of {@link #getCurIntentLocked()}. - */ - @GuardedBy("ImfLock.class") - private int getCurMethodUidLocked() { - return mBindingController.getCurMethodUid(); - } - - /** - * 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() { - return mBindingController.getLastBindTime(); + return getInputMethodBindingController(mCurrentUserId).getCurMethod(); } /** @@ -795,7 +744,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mRegistered = true; } - @Override public void onChange(boolean selfChange, Uri uri) { + @Override + public void onChange(boolean selfChange, Uri uri) { final Uri showImeUri = Settings.Secure.getUriFor( Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor( @@ -888,10 +838,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } for (int userId : mUserManagerInternal.getUserIds()) { final InputMethodSettings settings = queryInputMethodServicesInternal( - mContext, - userId, - AdditionalSubtypeMapRepository.get(userId), - DirectBootAwareness.AUTO); + mContext, + userId, + AdditionalSubtypeMapRepository.get(userId), + DirectBootAwareness.AUTO); InputMethodSettingsRepository.put(userId, settings); } postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */); @@ -903,37 +853,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final class MyPackageMonitor extends PackageMonitor { /** - * Package names that are known to contain {@link InputMethodService}. - * - * <p>No need to include packages because of direct-boot unaware IMEs since we always rescan - * all the packages when the user is unlocked, and direct-boot awareness will not be changed - * dynamically unless the entire package is updated, which also always triggers package - * rescanning.</p> - */ - @GuardedBy("ImfLock.class") - private final ArraySet<String> mKnownImePackageNames = new ArraySet<>(); - - /** - * Packages that are appeared, disappeared, or modified for whatever reason. - * - * <p>Note: For now we intentionally use {@link ArrayList} instead of {@link ArraySet} - * because 1) the number of elements is almost always 1 or so, and 2) we do not care - * duplicate elements for our use case.</p> - * - * <p>This object must be accessed only from callback methods in {@link PackageMonitor}, - * which should be bound to {@link #getRegisteredHandler()}.</p> - */ - private final ArrayList<String> mChangedPackages = new ArrayList<>(); - - /** - * {@code true} if one or more packages that contain {@link InputMethodService} appeared. - * - * <p>This field must be accessed only from callback methods in {@link PackageMonitor}, - * which should be bound to {@link #getRegisteredHandler()}.</p> - */ - private boolean mImePackageAppeared = false; - - /** * Remembers package names passed to {@link #onPackageDataCleared(String, int)}. * * <p>This field must be accessed only from callback methods in {@link PackageMonitor}, @@ -946,16 +865,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void clearKnownImePackageNamesLocked() { - mKnownImePackageNames.clear(); - } - - @GuardedBy("ImfLock.class") - void addKnownImePackageNameLocked(@NonNull String packageName) { - mKnownImePackageNames.add(packageName); - } - - @GuardedBy("ImfLock.class") private boolean isChangingPackagesOfCurrentUserLocked() { final int userId = getChangingUserId(); final boolean retval = userId == mCurrentUserId; @@ -1005,52 +914,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @Override - public void onPackageAppeared(String packageName, int reason) { - if (!mImePackageAppeared) { - final PackageManager pm = mContext.getPackageManager(); - final List<ResolveInfo> services = pm.queryIntentServicesAsUser( - new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName), - PackageManager.MATCH_DISABLED_COMPONENTS, getChangingUserId()); - // No need to lock this because we access it only on getRegisteredHandler(). - if (!services.isEmpty()) { - mImePackageAppeared = true; - } - } - // No need to lock this because we access it only on getRegisteredHandler(). - mChangedPackages.add(packageName); - } - - @Override - public void onPackageDisappeared(String packageName, int reason) { - // No need to lock this because we access it only on getRegisteredHandler(). - mChangedPackages.add(packageName); - } - - @Override - public void onPackageModified(String packageName) { - // No need to lock this because we access it only on getRegisteredHandler(). - mChangedPackages.add(packageName); - } - - @Override - public void onPackagesSuspended(String[] packages) { - // No need to lock this because we access it only on getRegisteredHandler(). - for (String packageName : packages) { - mChangedPackages.add(packageName); - } - } - - @Override - public void onPackagesUnsuspended(String[] packages) { - // No need to lock this because we access it only on getRegisteredHandler(). - for (String packageName : packages) { - mChangedPackages.add(packageName); - } - } - - @Override public void onPackageDataCleared(String packageName, int uid) { - mChangedPackages.add(packageName); mDataClearedPackages.add(packageName); } @@ -1062,37 +926,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void clearPackageChangeState() { // No need to lock them because we access these fields only on getRegisteredHandler(). - mChangedPackages.clear(); mDataClearedPackages.clear(); - mImePackageAppeared = false; } - @GuardedBy("ImfLock.class") - private boolean shouldRebuildInputMethodListLocked() { - // This method is guaranteed to be called only by getRegisteredHandler(). - - // If there is any new package that contains at least one IME, then rebuilt the list - // of IMEs. - if (mImePackageAppeared) { - return true; - } + private void onFinishPackageChangesInternal() { + final int userId = getChangingUserId(); - // Otherwise, check if mKnownImePackageNames and mChangedPackages have any intersection. - // TODO: Consider to create a utility method to do the following test. List.retainAll() - // is an option, but it may still do some extra operations that we do not need here. - final int numPackages = mChangedPackages.size(); - for (int i = 0; i < numPackages; ++i) { - final String packageName = mChangedPackages.get(i); - if (mKnownImePackageNames.contains(packageName)) { - return true; - } - } - return false; - } + // Instantiating InputMethodInfo requires disk I/O. + // Do them before acquiring the lock to minimize the chances of ANR (b/340221861). + final var newMethodMapWithoutAdditionalSubtypes = + queryInputMethodServicesInternal(mContext, userId, + AdditionalSubtypeMap.EMPTY_MAP, DirectBootAwareness.AUTO) + .getMethodMap(); - private void onFinishPackageChangesInternal() { synchronized (ImfLock.class) { - final int userId = getChangingUserId(); final boolean isCurrentUser = (userId == mCurrentUserId); final AdditionalSubtypeMap additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); @@ -1140,13 +987,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap, settings.getMethodMap()); } - if (isCurrentUser - && !(additionalSubtypeChanged || shouldRebuildInputMethodListLocked())) { + + final var newMethodMap = newMethodMapWithoutAdditionalSubtypes + .applyAdditionalSubtypes(newAdditionalSubtypeMap); + + if (InputMethodMap.areSame(settings.getMethodMap(), newMethodMap)) { + // No update in the actual IME map. return; } - final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, - userId, newAdditionalSubtypeMap, DirectBootAwareness.AUTO); + final InputMethodSettings newSettings = + InputMethodSettings.create(newMethodMap, userId); InputMethodSettingsRepository.put(userId, newSettings); if (!isCurrentUser) { return; @@ -1245,8 +1096,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public static final class Lifecycle extends SystemService { private final InputMethodManagerService mService; + public Lifecycle(Context context) { - this(context, new InputMethodManagerService(context)); + this(context, new InputMethodManagerService(context, + shouldEnableExperimentalConcurrentMultiUserMode(context))); } public Lifecycle( @@ -1273,6 +1126,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { // Called on ActivityManager thread. synchronized (ImfLock.class) { + if (mService.mExperimentalConcurrentMultiUserModeEnabled) { + // In concurrent multi-user mode, we in general do not rely on the concept of + // current user. + return; + } mService.scheduleSwitchUserTaskLocked(to.getUserIdentifier(), /* clientToBeReset= */ null); } @@ -1298,9 +1156,15 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void onUserStarting(TargetUser user) { // Called on ActivityManager thread. - SecureSettingsWrapper.onUserStarting(user.getUserIdentifier()); + final int userId = user.getUserIdentifier(); + SecureSettingsWrapper.onUserStarting(userId); synchronized (ImfLock.class) { - mService.mUserDataRepository.getOrCreate(user.getUserIdentifier()); + mService.mUserDataRepository.getOrCreate(userId); + if (mService.mExperimentalConcurrentMultiUserModeEnabled) { + if (mService.mCurrentUserId != userId) { + mService.experimentalInitializeVisibleBackgroundUserLocked(userId); + } + } } } @@ -1321,6 +1185,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // We need to rebuild IMEs. postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); updateInputMethodsFromSettingsLocked(true /* enabledChanged */); + } else if (mExperimentalConcurrentMultiUserModeEnabled) { + experimentalInitializeVisibleBackgroundUserLocked(userId); } } } @@ -1345,16 +1211,21 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mHandler.post(task); } - public InputMethodManagerService(Context context) { - this(context, null, null); + public InputMethodManagerService(Context context, + boolean experimentalConcurrentMultiUserModeEnabled) { + this(context, experimentalConcurrentMultiUserModeEnabled, null, null, null); } @VisibleForTesting InputMethodManagerService( Context context, + boolean experimentalConcurrentMultiUserModeEnabled, @Nullable ServiceThread serviceThreadForTesting, - @Nullable InputMethodBindingController bindingControllerForTesting) { + @Nullable ServiceThread packageMonitorThreadForTesting, + @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) { synchronized (ImfLock.class) { + mExperimentalConcurrentMultiUserModeEnabled = + experimentalConcurrentMultiUserModeEnabled; mContext = context; mRes = context.getResources(); SecureSettingsWrapper.onStart(mContext); @@ -1370,6 +1241,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. true /* allowIo */); thread.start(); mHandler = Handler.createAsync(thread.getLooper(), this); + { + final ServiceThread packageMonitorThread = + packageMonitorThreadForTesting != null + ? packageMonitorThreadForTesting + : new ServiceThread( + PACKAGE_MONITOR_THREAD_NAME, + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); + packageMonitorThread.start(); + mPackageMonitorHandler = Handler.createAsync(packageMonitorThread.getLooper()); + } SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mHandler); mImeTrackerService = new ImeTrackerService(serviceThreadForTesting != null ? serviceThreadForTesting.getLooper() : Looper.getMainLooper()); @@ -1392,7 +1274,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. AdditionalSubtypeMapRepository.initialize(mHandler, mContext); mCurrentUserId = mActivityManagerInternal.getCurrentUserId(); - mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal); + @SuppressWarnings("GuardedBy") final IntFunction<InputMethodBindingController> + bindingControllerFactory = userId -> new InputMethodBindingController(userId, + InputMethodManagerService.this); + mUserDataRepository = new UserDataRepository(mHandler, mUserManagerInternal, + bindingControllerForTesting != null ? bindingControllerForTesting + : bindingControllerFactory); for (int id : mUserManagerInternal.getUserIds()) { mUserDataRepository.getOrCreate(id); } @@ -1406,12 +1293,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. new HardwareKeyboardShortcutController(settings.getMethodMap(), settings.getUserId()); mMenuController = new InputMethodMenuController(this); - mBindingController = - bindingControllerForTesting != null - ? bindingControllerForTesting - : new InputMethodBindingController(this); - mAutofillController = new AutofillSuggestionsController(this); - mVisibilityStateComputer = new ImeVisibilityStateComputer(this); mVisibilityApplier = new DefaultImeVisibilityApplier(this); @@ -1544,9 +1425,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Note that in b/197848765 we want to see if we can keep the binding alive for better // profile switching. - mBindingController.unbindCurrentMethod(); - // TODO(b/325515685): No need to do this once BindingController becomes per-user. - mBindingController.setSelectedMethodId(null); + final var bindingController = getInputMethodBindingController(mCurrentUserId); + bindingController.unbindCurrentMethod(); + unbindCurrentClientLocked(UnbindReason.SWITCH_USER); // Hereafter we start initializing things for "newUserId". @@ -1643,7 +1524,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } }, "Lazily initialize IMMS#mImeDrawsImeNavBarRes"); - mMyPackageMonitor.register(mContext, UserHandle.ALL, mHandler); + mMyPackageMonitor.register(mContext, UserHandle.ALL, mPackageMonitorHandler); mSettingsObserver.registerContentObserverLocked(currentUserId); final IntentFilter broadcastFilterForAllUsers = new IntentFilter(); @@ -1671,8 +1552,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Returns true iff the caller is identified to be the current input method with the token. - * @param token The window token given to the input method when it was started. - * @return true if and only if non-null valid token is specified. + * + * @param token the window token given to the input method when it was started + * @return true if and only if non-null valid token is specified */ @GuardedBy("ImfLock.class") private boolean calledWithValidTokenLocked(@NonNull IBinder token) { @@ -1763,9 +1645,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Check if selected IME of current user supports handwriting. if (userId == mCurrentUserId) { - return mBindingController.supportsStylusHandwriting() + final var bindingController = getInputMethodBindingController(userId); + return bindingController.supportsStylusHandwriting() && (!connectionless - || mBindingController.supportsConnectionlessStylusHandwriting()); + || bindingController.supportsConnectionlessStylusHandwriting()); } final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final InputMethodInfo imi = settings.getMethodMap().get( @@ -1818,33 +1701,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return methodList; } - @GuardedBy("ImfLock.class") - void performOnCreateInlineSuggestionsRequestLocked() { - mAutofillController.performOnCreateInlineSuggestionsRequest(); - } - - /** - * 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}. + * @param imiId if null, returns enabled subtypes for the current + * {@link InputMethodInfo} * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled - * subtypes. - * @param userId the user ID to be queried about. + * subtypes + * @param userId the user ID to be queried about */ @Override public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, @@ -1888,10 +1752,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * <p>As a general principle, IPCs from the application process that take * {@link IInputMethodClient} will be rejected without this step.</p> * - * @param client {@link android.os.Binder} proxy that is associated with the singleton instance - * of {@link android.view.inputmethod.InputMethodManager} that runs on the client - * process - * @param inputConnection communication channel for the fallback {@link InputConnection} + * @param client {@link android.os.Binder} proxy that is associated with the + * singleton instance of + * {@link android.view.inputmethod.InputMethodManager} that runs + * on the client process + * @param inputConnection communication channel for the fallback {@link InputConnection} * @param selfReportedDisplayId self-reported display ID to which the client is associated. * Whether the client is still allowed to access to this display * or not needs to be evaluated every time the client interacts @@ -1916,10 +1781,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } - // TODO(b/325515685): Move this method to InputMethodBindingController /** * Hide the IME if the removed user is the current user. */ + // TODO(b/325515685): Move this method to InputMethodBindingController @GuardedBy("ImfLock.class") private void onClientRemoved(ClientState client) { clearClientSessionLocked(client); @@ -1952,7 +1817,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) { @@ -1972,7 +1836,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 bindingController = getInputMethodBindingController(mCurrentUserId); + mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(), + unbindClientReason); mCurClient.mSessionRequested = false; mCurClient.mSessionRequestedForAccessibility = false; mCurClient = null; @@ -2050,12 +1920,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean restarting = !initial; final Binder startInputToken = new Binder(); + final var bindingController = getInputMethodBindingController(mCurrentUserId); final StartInputInfo info = new StartInputInfo(mCurrentUserId, getCurTokenLocked(), - mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, - UserHandle.getUserId(mCurClient.mUid), + getCurTokenDisplayIdLocked(), bindingController.getCurId(), startInputReason, + restarting, UserHandle.getUserId(mCurClient.mUid), mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo, - mImeBindingState.mFocusedWindowSoftInputMode, getSequenceNumberLocked()); + mImeBindingState.mFocusedWindowSoftInputMode, + bindingController.getSequenceNumber()); mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow); mStartInputHistory.addEntry(info); @@ -2066,8 +1938,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. if (mCurrentUserId == UserHandle.getUserId( mCurClient.mUid)) { - mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, - null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()), + mPackageManagerInternal.grantImplicitAccess(mCurrentUserId, null /* intent */, + UserHandle.getAppId(bindingController.getCurMethodUid()), mCurClient.mUid, true /* direct */); } @@ -2088,20 +1960,20 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } - String curId = getCurIdLocked(); + final var curId = bindingController.getCurId(); final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(mCurrentUserId) .getMethodMap().get(curId); final boolean suppressesSpellChecker = curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions); - if (mBindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) { + if (bindingController.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, bindingController.getSequenceNumber(), suppressesSpellChecker); } @GuardedBy("ImfLock.class") @@ -2153,7 +2025,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 InputMethodBindingController bindingController) { // Compute the final shown display ID with validated cs.selfReportedDisplayId for this // session & other conditions. @@ -2167,8 +2040,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Potentially override the selected input method if the new display belongs to a virtual // device with a custom IME. - String selectedMethodId = getSelectedMethodIdLocked(); - final String deviceMethodId = computeCurrentDeviceMethodIdLocked(selectedMethodId); + String selectedMethodId = bindingController.getSelectedMethodId(); + final String deviceMethodId = computeCurrentDeviceMethodIdLocked(bindingController.mUserId, + selectedMethodId); if (deviceMethodId == null) { mVisibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); } else if (!Objects.equals(deviceMethodId, selectedMethodId)) { @@ -2194,7 +2068,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final boolean connectionWasActive = mCurInputConnection != null; // Bump up the sequence for this client and attach it. - advanceSequenceNumberLocked(); + bindingController.advanceSequenceNumber(); + mCurClient = cs; mCurInputConnection = inputConnection; mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; @@ -2223,15 +2098,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (DEBUG) { Slog.d(TAG, "Avoiding IME startup and unbinding current input method."); } - invalidateAutofillSessionLocked(); - mBindingController.unbindCurrentMethod(); + bindingController.invalidateAutofillSession(); + bindingController.unbindCurrentMethod(); return InputBindResult.NO_EDITOR; } // 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 == getCurTokenDisplayIdLocked()) { if (cs.mCurSession != null) { // Fast case: if we are already connected to the input method, // then just return it. @@ -2250,27 +2127,29 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); } - InputBindResult bindResult = tryReuseConnectionLocked(cs); + InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs); if (bindResult != null) { return bindResult; } } - mBindingController.unbindCurrentMethod(); - - return mBindingController.bindCurrentMethod(); + bindingController.unbindCurrentMethod(); + return bindingController.bindCurrentMethod(); } /** * Update the current deviceId and return the relevant imeId for this device. - * 1. If the device changes to virtual and its custom IME is not available, then disable IME. - * 2. If the device changes to virtual with valid custom IME, then return the custom IME. If - * the old device was default, then store the current imeId so it can be restored. - * 3. If the device changes to default, restore the default device IME. - * 4. Otherwise keep the current imeId. + * + * <p>1. If the device changes to virtual and its custom IME is not available, then disable + * IME.</p> + * <p>2. If the device changes to virtual with valid custom IME, then return the custom IME. If + * the old device was default, then store the current imeId so it can be restored.</p> + * <p>3. If the device changes to default, restore the default device IME.</p> + * <p>4. Otherwise keep the current imeId.</p> */ @GuardedBy("ImfLock.class") - private String computeCurrentDeviceMethodIdLocked(String currentMethodId) { + private String computeCurrentDeviceMethodIdLocked(@UserIdInt int userId, + String currentMethodId) { if (mVdmInternal == null) { mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class); } @@ -2278,7 +2157,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return currentMethodId; } - final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); + final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); final int oldDeviceId = mDeviceIdToShowIme; mDeviceIdToShowIme = mVdmInternal.getDeviceIdForDisplayId(mDisplayIdToShowIme); if (mDeviceIdToShowIme == DEVICE_ID_DEFAULT) { @@ -2320,11 +2199,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } @GuardedBy("ImfLock.class") - void invalidateAutofillSessionLocked() { - mAutofillController.invalidateAutofillSession(); - } - - @GuardedBy("ImfLock.class") private boolean shouldPreventImeStartupLocked( @NonNull String selectedMethodId, @StartInputFlags int startInputFlags, @@ -2351,13 +2225,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. @@ -2370,8 +2237,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") @Nullable - private InputBindResult tryReuseConnectionLocked(@NonNull ClientState cs) { - if (hasConnectionLocked()) { + private InputBindResult tryReuseConnectionLocked( + @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs) { + if (bindingController.hasMainConnection()) { if (getCurMethodLocked() != null) { // Return to client, and we will get back with it when // we have had a session made for it. @@ -2379,9 +2247,12 @@ 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, + bindingController.getCurId(), + bindingController.getSequenceNumber(), false); } else { - long bindingDuration = SystemClock.uptimeMillis() - getLastBindTimeLocked(); + final long lastBindTime = bindingController.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 @@ -2392,7 +2263,9 @@ 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, + bindingController.getCurId(), + bindingController.getSequenceNumber(), false); } else { EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, getSelectedMethodIdLocked(), bindingDuration, 0); @@ -2404,18 +2277,19 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @FunctionalInterface interface ImeDisplayValidator { - @DisplayImePolicy int getDisplayImePolicy(int displayId); + @DisplayImePolicy + int getDisplayImePolicy(int displayId); } /** * Find the display where the IME should be shown. * - * @param displayId the ID of the display where the IME client target is. - * @param checker instance of {@link ImeDisplayValidator} which is used for - * checking display config to adjust the final target display. - * @return The ID of the display where the IME should be shown or - * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of - * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}. + * @param displayId the ID of the display where the IME client target is + * @param checker instance of {@link ImeDisplayValidator} which is used for + * checking display config to adjust the final target display + * @return the ID of the display where the IME should be shown or + * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of + * {@link WindowManager#DISPLAY_IME_POLICY_HIDE} */ static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { @@ -2438,7 +2312,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) { if (DEBUG) { Slog.v(TAG, "Sending attach of token: " + token + " for display: " - + mCurTokenDisplayId); + + getCurTokenDisplayIdLocked()); } inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token), getInputMethodNavButtonFlagsLocked()); @@ -2512,17 +2386,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mImeWindowVis = 0; mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; updateSystemUiLocked(mImeWindowVis, mBackDisposition); - mCurTokenDisplayId = INVALID_DISPLAY; - mCurHostInputToken = null; } @GuardedBy("ImfLock.class") void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) { - mBindingController.setSelectedMethodId(null); + final var bindingController = + mUserDataRepository.getOrCreate(mCurrentUserId).mBindingController; + bindingController.setSelectedMethodId(null); + // Callback before clean-up binding states. // TODO(b/338461930): Check if this is still necessary or not. onUnbindCurrentMethodByReset(); - mBindingController.unbindCurrentMethod(); + bindingController.unbindCurrentMethod(); unbindCurrentClientLocked(unbindClientReason); } @@ -2697,7 +2572,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. : null; if (mStatusBarManagerInternal != null) { mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0, - contentDescription != null + contentDescription != null ? contentDescription.toString() : null); mStatusBarManagerInternal.setIconVisibility(mSlotIme, true); } @@ -2725,9 +2600,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Whether the current display has a navigation bar. When this is false (e.g. emulator), // the IME should not draw the IME navigation bar. + final int tokenDisplayId = getCurTokenDisplayIdLocked(); final boolean hasNavigationBar = mWindowManagerInternal - .hasNavigationBar(mCurTokenDisplayId != INVALID_DISPLAY - ? mCurTokenDisplayId : DEFAULT_DISPLAY); + .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY + ? tokenDisplayId : DEFAULT_DISPLAY); final boolean canImeDrawsImeNavBar = mImeDrawsImeNavBarRes != null && mImeDrawsImeNavBarRes.get() && hasNavigationBar; final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked( @@ -2743,7 +2619,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // When the IME switcher dialog is shown, the IME switcher button should be hidden. if (mMenuController.getSwitchingDialogLocked() != null) return false; // When we are switching IMEs, the IME switcher button should be hidden. - if (!Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) { + final var bindingController = getInputMethodBindingController(mCurrentUserId); + if (!Objects.equals(bindingController.getCurId(), + bindingController.getSelectedMethodId())) { return false; } if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() @@ -2822,8 +2700,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // Note that we still need to update IME status when focusing external display // that does not support system decoration and fallback to show IME on default // display since it is intentional behavior. - if (mCurTokenDisplayId != topFocusedDisplayId - && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) { + final int tokenDisplayId = getCurTokenDisplayIdLocked(); + if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) { return; } mImeWindowVis = vis; @@ -2886,7 +2764,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Slog.d(TAG, "IME window vis: " + vis + " active: " + (vis & InputMethodService.IME_ACTIVE) + " inv: " + (vis & InputMethodService.IME_INVISIBLE) - + " displayId: " + mCurTokenDisplayId); + + " displayId: " + getCurTokenDisplayIdLocked()); } final IBinder focusedWindowToken = mImeBindingState != null ? mImeBindingState.mFocusedWindow : null; @@ -2905,15 +2783,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } else { vis &= ~InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; } + final var curId = getInputMethodBindingController(mCurrentUserId).getCurId(); if (mMenuController.getSwitchingDialogLocked() != null - || !Objects.equals(getCurIdLocked(), getSelectedMethodIdLocked())) { + || !Objects.equals(curId, getSelectedMethodIdLocked())) { // When the IME switcher dialog is shown, or we are switching IMEs, // the back button should be in the default state (as if the IME is not shown). backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING; } final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); if (mStatusBarManagerInternal != null) { - mStatusBarManagerInternal.setImeWindowStatus(mCurTokenDisplayId, + mStatusBarManagerInternal.setImeWindowStatus(getCurTokenDisplayIdLocked(), getCurTokenLocked(), vis, backDisposition, needsToShowImeSwitcher); } } finally { @@ -2927,6 +2806,48 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mMenuController.updateKeyboardFromSettingsLocked(); } + /** + * This is an experimental implementation used when and only when + * {@link #mExperimentalConcurrentMultiUserModeEnabled}. + * + * <p>Never assume what this method is doing is officially supported. For the canonical and + * desired behaviors always refer to single-user code paths such as + * {@link #updateInputMethodsFromSettingsLocked(boolean)}.</p> + * + * <p>Here are examples of missing features.</p> + * <ul> + * <li>Subtypes are not supported at all!</li> + * <li>Profiles are not supported.</li> + * <li> + * {@link PackageManager#COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED} is not updated. + * </li> + * <li>{@link #mDeviceIdToShowIme} is ignored.</li> + * <li>{@link #mSwitchingController} is ignored.</li> + * <li>{@link #mHardwareKeyboardShortcutController} is ignored.</li> + * <li>{@link #mPreventImeStartupUnlessTextEditor} is ignored.</li> + * <li>and so on.</li> + * </ul> + */ + @GuardedBy("ImfLock.class") + void experimentalInitializeVisibleBackgroundUserLocked(@UserIdInt int userId) { + if (!mUserManagerInternal.isUserVisible(userId)) { + return; + } + final var settings = InputMethodSettingsRepository.get(userId); + String id = settings.getSelectedInputMethod(); + if (TextUtils.isEmpty(id)) { + final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME( + settings.getEnabledInputMethodList()); + if (imi == null) { + return; + } + id = imi.getId(); + settings.putSelectedInputMethod(id); + } + final var bindingController = getInputMethodBindingController(userId); + bindingController.setSelectedMethodId(id); + } + @GuardedBy("ImfLock.class") void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) { final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); @@ -3099,7 +3020,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() // because mCurMethodId is stored as a history in // setSelectedInputMethodAndSubtypeLocked(). - mBindingController.setSelectedMethodId(id); + getInputMethodBindingController(mCurrentUserId).setSelectedMethodId(id); if (mActivityManagerInternal.isSystemReady()) { Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); @@ -3154,7 +3075,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Nullable String delegatorPackageName, @NonNull IConnectionlessHandwritingCallback callback) { synchronized (ImfLock.class) { - if (!mBindingController.supportsConnectionlessStylusHandwriting()) { + final var bindingController = getInputMethodBindingController(userId); + if (!bindingController.supportsConnectionlessStylusHandwriting()) { Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME."); try { callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED); @@ -3237,7 +3159,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final long ident = Binder.clearCallingIdentity(); try { - if (!mBindingController.supportsStylusHandwriting()) { + final var bindingController = getInputMethodBindingController(mCurrentUserId); + if (!bindingController.supportsStylusHandwriting()) { Slog.w(TAG, "Stylus HW unsupported by IME. Ignoring startStylusHandwriting()"); return false; @@ -3420,7 +3343,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mVisibilityStateComputer.requestImeVisibility(windowToken, true); // Ensure binding the connection when IME is going to show. - mBindingController.setCurrentMethodVisible(); + final var bindingController = getInputMethodBindingController(mCurrentUserId); + bindingController.setCurrentMethodVisible(); final IInputMethodInvoker curMethod = getCurMethodLocked(); ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); final boolean readyToDispatchToIme; @@ -3528,7 +3452,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } else { ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE); } - mBindingController.setCurrentMethodNotVisible(); + final var bindingController = getInputMethodBindingController(mCurrentUserId); + bindingController.setCurrentMethodNotVisible(); mVisibilityStateComputer.clearImeShowFlags(); // Cancel existing statsToken for show IME as we got a hide request. ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); @@ -3596,12 +3521,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper); final InputBindResult result; synchronized (ImfLock.class) { + final var bindingController = getInputMethodBindingController(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(), + bindingController.getSequenceNumber(), false /* isInputMethodSuppressingSpellChecker */); } final ClientState cs = mClientController.getClient(client.asBinder()); @@ -3611,7 +3538,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final long ident = Binder.clearCallingIdentity(); try { // Verify if IMMS is in the process of switching user. - if (mUserSwitchHandlerTask != null) { + if (!mExperimentalConcurrentMultiUserModeEnabled + && mUserSwitchHandlerTask != null) { // There is already an on-going pending user switch task. final int nextUserId = mUserSwitchHandlerTask.mToUserId; if (userId == nextUserId) { @@ -3665,7 +3593,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // Verify if caller is a background user. - if (userId != mCurrentUserId) { + if (!mExperimentalConcurrentMultiUserModeEnabled && userId != mCurrentUserId) { if (ArrayUtils.contains( mUserManagerInternal.getProfileIds(mCurrentUserId, false), userId)) { @@ -3693,7 +3621,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, editorInfo, inputConnection, remoteAccessibilityInputConnection, - unverifiedTargetSdkVersion, userId, imeDispatcher, cs); + unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs); } finally { Binder.restoreCallingIdentity(ident); } @@ -3721,7 +3649,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 InputMethodBindingController bindingController, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs) { if (DEBUG) { Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason=" @@ -3734,7 +3662,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode) + " windowFlags=#" + Integer.toHexString(windowFlags) + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion - + " userId=" + userId + + " bindingController=" + bindingController + " imeDispatcher=" + imeDispatcher + " cs=" + cs); } @@ -3763,14 +3691,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (editorInfo != null) { return startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, - startInputReason, unverifiedTargetSdkVersion, imeDispatcher); + startInputReason, unverifiedTargetSdkVersion, imeDispatcher, + bindingController); } return new InputBindResult( InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, null, null, null, null, -1, false); } - mImeBindingState = new ImeBindingState(windowToken, softInputMode, cs, editorInfo); + mImeBindingState = new ImeBindingState(bindingController.mUserId, windowToken, + softInputMode, cs, editorInfo); mFocusedWindowPerceptible.put(windowToken, true); // We want to start input before showing the IME, but after closing @@ -3795,7 +3725,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); + imeDispatcher, bindingController); didStart = true; } break; @@ -3809,8 +3739,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. // window token removed. // 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) { - mBindingController.unbindCurrentMethod(); + if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) { + bindingController.unbindCurrentMethod(); } } } @@ -3819,7 +3749,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); + imeDispatcher, bindingController); } else { res = InputBindResult.NULL_EDITOR_INFO; } @@ -3860,10 +3790,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mCurrentUserId != UserHandle.getUserId(uid)) { return false; } - if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid( - mPackageManagerInternal, - uid, - getCurIntentLocked().getComponent().getPackageName())) { + final var curIntent = getInputMethodBindingController(mCurrentUserId).getCurIntent(); + if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid( + mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) { return true; } return false; @@ -4173,7 +4102,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * This is kept due to {@code @UnsupportedAppUsage} in * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in * {@link InputMethodService#onCreate()}. - * * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)} * * @deprecated TODO(b/113914148): Check if we can remove this @@ -4191,7 +4119,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } // This should probably use the caller's display id, but because this is unsupported // and maintained only for compatibility, there's no point in fixing it. - curTokenDisplayId = mCurTokenDisplayId; + curTokenDisplayId = getCurTokenDisplayIdLocked(); } return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId); }); @@ -4271,8 +4199,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. mStylusIds.add(deviceId); // a new Stylus is detected. If IME supports handwriting, and we don't have // handwriting initialized, lets do it now. + final var bindingController = getInputMethodBindingController(mCurrentUserId); if (!mHwController.getCurrentRequestId().isPresent() - && mBindingController.supportsStylusHandwriting()) { + && bindingController.supportsStylusHandwriting()) { scheduleResetStylusHandwriting(); } } @@ -4330,7 +4259,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Helper method to set a stylus idle-timeout after which handwriting {@code InkWindow} * will be removed. - * @param timeout to set in milliseconds. To reset to default, use a value <= zero. + * + * @param timeout to set in milliseconds. To reset to default, use a value <= zero */ @BinderThread @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) @@ -4361,11 +4291,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private static IntArray getStylusInputDeviceIds(InputManager im) { IntArray stylusIds = new IntArray(); for (int id : im.getInputDeviceIds()) { - if (!im.isInputDeviceEnabled(id)) { - continue; - } InputDevice device = im.getInputDevice(id); - if (device != null && isStylusDevice(device)) { + if (device != null && device.isEnabled() && isStylusDevice(device)) { stylusIds.add(id); } } @@ -4453,9 +4380,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private void dumpDebug(ProtoOutputStream proto, long fieldId) { synchronized (ImfLock.class) { + final var bindingController = getInputMethodBindingController(mCurrentUserId); final long token = proto.start(fieldId); proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked()); - proto.write(CUR_SEQ, getSequenceNumberLocked()); + proto.write(CUR_SEQ, bindingController.getSequenceNumber()); proto.write(CUR_CLIENT, Objects.toString(mCurClient)); mImeBindingState.dumpDebug(proto, mWindowManagerInternal); proto.write(LAST_IME_TARGET_WINDOW_NAME, @@ -4465,13 +4393,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mCurEditorInfo != null) { mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } - proto.write(CUR_ID, getCurIdLocked()); + proto.write(CUR_ID, bindingController.getCurId()); mVisibilityStateComputer.dumpDebug(proto, fieldId); proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode); proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); - proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId); + proto.write(CUR_TOKEN_DISPLAY_ID, getCurTokenDisplayIdLocked()); proto.write(SYSTEM_READY, mSystemReady); - proto.write(HAVE_CONNECTION, hasConnectionLocked()); + proto.write(HAVE_CONNECTION, bindingController.hasMainConnection()); proto.write(BOUND_TO_METHOD, mBoundToMethod); proto.write(IS_INTERACTIVE, mIsInteractive); proto.write(BACK_DISPOSITION, mBackDisposition); @@ -4583,7 +4511,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken); final WindowManagerInternal.ImeTargetInfo info = mWindowManagerInternal.onToggleImeRequested( - show, mImeBindingState.mFocusedWindow, requestToken, mCurTokenDisplayId); + show, mImeBindingState.mFocusedWindow, requestToken, + getCurTokenDisplayIdLocked()); mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo, info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason, @@ -4684,7 +4613,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>(); for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) { if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) { - AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i); + AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i); if (sessionState != null) { disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i), sessionState.mSession); @@ -4841,10 +4770,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. case MSG_RESET_HANDWRITING: { synchronized (ImfLock.class) { - if (mBindingController.supportsStylusHandwriting() + final var bindingController = getInputMethodBindingController(mCurrentUserId); + if (bindingController.supportsStylusHandwriting() && getCurMethodLocked() != null && hasSupportedStylusLocked()) { Slog.d(TAG, "Initializing Handwriting Spy"); - mHwController.initializeHandwritingSpy(mCurTokenDisplayId); + mHwController.initializeHandwritingSpy(getCurTokenDisplayIdLocked()); } else { mHwController.reset(); } @@ -4866,11 +4796,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (curMethod == null || mImeBindingState.mFocusedWindow == null) { return true; } + final var bindingController = getInputMethodBindingController(mCurrentUserId); final HandwritingModeController.HandwritingSession session = mHwController.startHandwritingSession( msg.arg1 /*requestId*/, msg.arg2 /*pid*/, - mBindingController.getCurMethodUid(), + bindingController.getCurMethodUid(), mImeBindingState.mFocusedWindow); if (session == null) { Slog.e(TAG, @@ -4920,7 +4851,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (mCurClient == null || mCurClient.mClient == null) { return; } - if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol(getCurMethodUidLocked())) { + // TODO(b/325515685): user data must be retrieved by a userId parameter + final var bindingController = getInputMethodBindingController(mCurrentUserId); + if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol( + bindingController.getCurMethodUid())) { // Handle IME visibility when interactive changed before finishing the input to // ensure we preserve the last state as possible. final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged( @@ -5055,30 +4989,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. return; } mMethodMapUpdateCount++; - mMyPackageMonitor.clearKnownImePackageNamesLocked(); final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId); - // Construct the set of possible IME packages for onPackageChanged() to avoid false - // negatives when the package state remains to be the same but only the component state is - // changed. - { - // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose - // of this query is to avoid false negatives. PackageManager.MATCH_ALL could be more - // conservative, but it seems we cannot use it for now (Issue 35176630). - final List<ResolveInfo> allInputMethodServices = - mContext.getPackageManager().queryIntentServicesAsUser( - new Intent(InputMethod.SERVICE_INTERFACE), - PackageManager.MATCH_DISABLED_COMPONENTS, settings.getUserId()); - final int numImes = allInputMethodServices.size(); - for (int i = 0; i < numImes; ++i) { - final ServiceInfo si = allInputMethodServices.get(i).serviceInfo; - if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) { - mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName); - } - } - } - boolean reenableMinimumNonAuxSystemImes = false; // TODO: The following code should find better place to live. if (!resetDefaultEnabledIme) { @@ -5164,7 +5077,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @GuardedBy("ImfLock.class") void sendOnNavButtonFlagsChangedLocked() { - final IInputMethodInvoker curMethod = mBindingController.getCurMethod(); + final var bindingController = getInputMethodBindingController(mCurrentUserId); + final IInputMethodInvoker curMethod = bindingController.getCurMethod(); if (curMethod == null) { // No need to send the data if the IME is not yet bound. return; @@ -5209,10 +5123,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}. * - * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently not - * recognized by the system. - * @param enabled {@code true} if {@code id} needs to be enabled. - * @return {@code true} if the IME was previously enabled. {@code false} otherwise. + * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently + * not recognized by the system + * @param enabled {@code true} if {@code id} needs to be enabled + * @return {@code true} if the IME was previously enabled */ @GuardedBy("ImfLock.class") private boolean setInputMethodEnabledLocked(String id, boolean enabled) { @@ -5319,8 +5233,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Gets the current subtype of this input method. * - * @param userId User ID to be queried about. - * @return The current {@link InputMethodSubtype} for the specified user. + * @param userId User ID to be queried about + * @return the current {@link InputMethodSubtype} for the specified user */ @Nullable @Override @@ -5394,7 +5308,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Returns the default {@link InputMethodInfo} for the specific userId. - * @param userId user ID to query. + * + * @param userId user ID to query */ @GuardedBy("ImfLock.class") private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) { @@ -5416,7 +5331,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } if (!settings.getMethodMap().containsKey(imeId) || !settings.getEnabledInputMethodList().contains( - settings.getMethodMap().get(imeId))) { + settings.getMethodMap().get(imeId))) { return false; // IME is not found or not enabled. } settings.putSelectedInputMethod(imeId); @@ -5428,11 +5343,11 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * Filter the access to the input method by rules of the package visibility. Return {@code true} * if the given input method is the currently selected one or visible to the caller. * - * @param targetPkgName The package name of input method to check. - * @param callingUid The caller that is going to access the input method. - * @param userId The user ID where the input method resides. - * @param settings The input method settings under the given user ID. - * @return {@code true} if caller is able to access the input method. + * @param targetPkgName the package name of input method to check + * @param callingUid the caller that is going to access the input method + * @param userId the user ID where the input method resides + * @param settings the input method settings under the given user ID + * @return {@code true} if caller is able to access the input method */ private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid, @UserIdInt int userId, @NonNull InputMethodSettings settings) { @@ -5526,14 +5441,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, - InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) { + InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb) { // Get the device global touch exploration state before lock to avoid deadlock. final boolean touchExplorationEnabled = AccessibilityManagerInternal.get() .isTouchExplorationEnabled(userId); synchronized (ImfLock.class) { - mAutofillController.onCreateInlineSuggestionsRequest(userId, requestInfo, cb, - touchExplorationEnabled); + getInputMethodBindingController(userId).onCreateInlineSuggestionsRequest( + requestInfo, cb, touchExplorationEnabled); } } @@ -5594,14 +5509,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 != getCurTokenDisplayIdLocked()) { + return false; + } + curHostInputToken = getInputMethodBindingController(userId).getCurHostInputToken(); + if (curHostInputToken == null) { return false; } - curHostInputToken = mCurHostInputToken; } return mInputManagerInternal.transferTouchGesture(sourceInputToken, curHostInputToken); } @@ -5646,6 +5564,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void onSessionForAccessibilityCreated(int accessibilityConnectionId, IAccessibilityInputMethodSession session, @UserIdInt int userId) { synchronized (ImfLock.class) { + final var bindingController = getInputMethodBindingController(mCurrentUserId); // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId); @@ -5667,8 +5586,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, + bindingController.getCurId(), + bindingController.getSequenceNumber(), + /* isInputMethodSuppressingSpellChecker= */ false); mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId); } } @@ -5678,6 +5599,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, @UserIdInt int userId) { synchronized (ImfLock.class) { + final var bindingController = getInputMethodBindingController(mCurrentUserId); // TODO(b/305829876): Implement user ID verification if (mCurClient != null) { if (DEBUG) { @@ -5687,7 +5609,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(), + bindingController.getSequenceNumber(), accessibilityConnectionId); } // We only have sessions when we bound to an input method. Remove this session @@ -5897,13 +5819,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); @@ -5911,25 +5831,43 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. p.println(" pid=" + c.mPid); }; mClientController.forAllClients(clientControllerDump); + final var bindingController = getInputMethodBindingController(mCurrentUserId); p.println(" mCurrentUserId=" + mCurrentUserId); p.println(" mCurMethodId=" + getSelectedMethodIdLocked()); client = mCurClient; - p.println(" mCurClient=" + client + " mCurSeq=" + getSequenceNumberLocked()); + p.println(" mCurClient=" + client + " mCurSeq=" + + bindingController.getSequenceNumber()); p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); - mImeBindingState.dump(" ", p); - p.println(" mCurId=" + getCurIdLocked() + " mHaveConnection=" + hasConnectionLocked() + mImeBindingState.dump(/* prefix= */ " ", p); + + p.println(" mCurId=" + bindingController.getCurId() + + " mHaveConnection=" + bindingController.hasMainConnection() + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" - + mBindingController.isVisibleBound()); + + bindingController.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(" mCurIntent=" + getCurIntentLocked()); + p.println(" mCurTokenDisplayId=" + getCurTokenDisplayIdLocked()); + p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken()); + p.println(" mCurIntent=" + bindingController.getCurIntent()); method = getCurMethodLocked(); p.println(" mCurMethod=" + getCurMethodLocked()); p.println(" mEnabledSession=" + mEnabledSession); mVisibilityStateComputer.dump(pw, " "); p.println(" mInFullscreenMode=" + mInFullscreenMode); p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive); + p.println(" mExperimentalConcurrentMultiUserModeEnabled=" + + mExperimentalConcurrentMultiUserModeEnabled); p.println(" ENABLE_HIDE_IME_CAPTION_BAR=" + InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR); p.println(" mSettingsObserver=" + mSettingsObserver); @@ -6162,8 +6100,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell ime list}. - * @param shellCommand {@link ShellCommand} object that is handling this command. - * @return Exit code of the command. + * + * @param shellCommand {@link ShellCommand} object that is handling this command + * @return exit code of the command */ @BinderThread @ShellCommandResult @@ -6221,9 +6160,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell ime enable} and {@code adb shell ime disable}. * - * @param shellCommand {@link ShellCommand} object that is handling this command. - * @param enabled {@code true} if the command was {@code adb shell ime enable}. - * @return Exit code of the command. + * @param shellCommand {@link ShellCommand} object that is handling this command + * @param enabled {@code true} if the command was {@code adb shell ime enable} + * @return exit code of the command */ @BinderThread @ShellCommandResult @@ -6258,8 +6197,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. * {@link ShellCommand#getNextArg()} and {@link ShellCommand#getNextArgRequired()} for the * main arguments.</p> * - * @param shellCommand {@link ShellCommand} from which options should be obtained. - * @return User ID to be resolved. {@link UserHandle#CURRENT} if not specified. + * @param shellCommand {@link ShellCommand} from which options should be obtained + * @return user ID to be resolved. {@link UserHandle#CURRENT} if not specified */ @BinderThread @UserIdInt @@ -6281,12 +6220,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles core logic of {@code adb shell ime enable} and {@code adb shell ime disable}. * - * @param userId user ID specified to the command. Pseudo user IDs are not supported. - * @param imeId IME ID specified to the command. - * @param enabled {@code true} for {@code adb shell ime enable}. {@code false} otherwise. - * @param out {@link PrintWriter} to output standard messages. - * @param error {@link PrintWriter} to output error messages. - * @return {@code false} if it fails to enable the IME. {@code false} otherwise. + * @param userId user ID specified to the command (pseudo user IDs are not supported) + * @param imeId IME ID specified to the command + * @param enabled {@code true} for {@code adb shell ime enable} + * @param out {@link PrintWriter} to output standard messages + * @param error {@link PrintWriter} to output error messages + * @return {@code false} if it fails to enable the IME */ @BinderThread @GuardedBy("ImfLock.class") @@ -6344,7 +6283,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell ime set}. * - * @param shellCommand {@link ShellCommand} object that is handling this command. + * @param shellCommand {@link ShellCommand} object that is handling this command * @return Exit code of the command. */ @BinderThread @@ -6387,7 +6326,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell ime reset-ime}. - * @param shellCommand {@link ShellCommand} object that is handling this command. + * + * @param shellCommand {@link ShellCommand} object that is handling this command * @return Exit code of the command. */ @BinderThread @@ -6403,8 +6343,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. continue; } // Skip on headless user - if (USER_TYPE_SYSTEM_HEADLESS.equals( - mUserManagerInternal.getUserInfo(userId).userType)) { + final var userInfo = mUserManagerInternal.getUserInfo(userId); + if (userInfo != null && USER_TYPE_SYSTEM_HEADLESS.equals(userInfo.userType)) { continue; } final String nextIme; @@ -6413,7 +6353,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (userId == mCurrentUserId) { hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); - mBindingController.unbindCurrentMethod(); + final var bindingController = getInputMethodBindingController(userId); + bindingController.unbindCurrentMethod(); // Enable default IMEs, disable others var toDisable = settings.getEnabledInputMethodList(); @@ -6466,7 +6407,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}. - * @param shellCommand {@link ShellCommand} object that is handling this command. + * + * @param shellCommand {@link ShellCommand} object that is handling this command * @return Exit code of the command. */ @BinderThread @@ -6511,9 +6453,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * @param userId the actual user handle obtained by {@link UserHandle#getIdentifier()} - * and *not* pseudo ids like {@link UserHandle#USER_ALL etc}. - * @return {@code true} if userId has debugging privileges. - * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}. + * and *not* pseudo ids like {@link UserHandle#USER_ALL etc} + * @return {@code true} if userId has debugging privileges + * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false} */ private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) { if (mUserManagerInternal.hasUserRestriction( @@ -6534,8 +6476,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. /** * Creates an IME request tracking token for the current focused client. * - * @param show whether this is a show or a hide request. - * @param reason the reason why the IME request was created. + * @param show whether this is a show or a hide request + * @param reason the reason why the IME request was created */ @NonNull private ImeTracker.Token createStatsTokenForFocusedClient(boolean show, @@ -6557,6 +6499,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. private final InputMethodManagerService mImms; @NonNull private final IBinder mToken; + InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms, @NonNull IBinder token) { mImms = imms; @@ -6585,8 +6528,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void createInputContentUriToken(Uri contentUri, String packageName, AndroidFuture future /* T=IBinder */) { - @SuppressWarnings("unchecked") - final AndroidFuture<IBinder> typedFuture = future; + @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future; try { typedFuture.complete(mImms.createInputContentUriToken( mToken, contentUri, packageName).asBinder()); @@ -6604,8 +6546,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @Override public void setInputMethod(String id, AndroidFuture future /* T=Void */) { - @SuppressWarnings("unchecked") - final AndroidFuture<Void> typedFuture = future; + @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { mImms.setInputMethod(mToken, id); typedFuture.complete(null); @@ -6618,8 +6559,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype, AndroidFuture future /* T=Void */) { - @SuppressWarnings("unchecked") - final AndroidFuture<Void> typedFuture = future; + @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { mImms.setInputMethodAndSubtype(mToken, id, subtype); typedFuture.complete(null); @@ -6633,8 +6573,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void hideMySoftInput(@NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) { - @SuppressWarnings("unchecked") - final AndroidFuture<Void> typedFuture = future; + @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { mImms.hideMySoftInput(mToken, statsToken, flags, reason); typedFuture.complete(null); @@ -6648,8 +6587,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. public void showMySoftInput(@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, AndroidFuture future /* T=Void */) { - @SuppressWarnings("unchecked") - final AndroidFuture<Void> typedFuture = future; + @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; try { mImms.showMySoftInput(mToken, statsToken, flags, reason); typedFuture.complete(null); @@ -6667,8 +6605,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @Override public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) { - @SuppressWarnings("unchecked") - final AndroidFuture<Boolean> typedFuture = future; + @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; try { typedFuture.complete(mImms.switchToPreviousInputMethod(mToken)); } catch (Throwable e) { @@ -6680,8 +6617,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @Override public void switchToNextInputMethod(boolean onlyCurrentIme, AndroidFuture future /* T=Boolean */) { - @SuppressWarnings("unchecked") - final AndroidFuture<Boolean> typedFuture = future; + @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; try { typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme)); } catch (Throwable e) { @@ -6692,8 +6628,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @BinderThread @Override public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) { - @SuppressWarnings("unchecked") - final AndroidFuture<Boolean> typedFuture = future; + @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; try { typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken)); } catch (Throwable e) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMap.java b/services/core/java/com/android/server/inputmethod/InputMethodMap.java index a8e5e2ef4f72..bab21e8b7b06 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMap.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMap.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.util.ArrayMap; import android.view.inputmethod.InputMethodInfo; +import java.util.Arrays; import java.util.List; /** @@ -75,4 +76,61 @@ final class InputMethodMap { int size() { return mMap.size(); } + + @AnyThread + @NonNull + public InputMethodMap applyAdditionalSubtypes( + @NonNull AdditionalSubtypeMap additionalSubtypeMap) { + if (additionalSubtypeMap.isEmpty()) { + return this; + } + final int size = size(); + final ArrayMap<String, InputMethodInfo> newMethodMap = new ArrayMap<>(size); + boolean updated = false; + for (int i = 0; i < size; ++i) { + final var imi = valueAt(i); + final var imeId = imi.getId(); + final var newAdditionalSubtypes = additionalSubtypeMap.get(imeId); + if (newAdditionalSubtypes == null || newAdditionalSubtypes.isEmpty()) { + newMethodMap.put(imi.getId(), imi); + } else { + newMethodMap.put(imi.getId(), new InputMethodInfo(imi, newAdditionalSubtypes)); + updated = true; + } + } + return updated ? InputMethodMap.of(newMethodMap) : this; + } + + /** + * Compares the given two {@link InputMethodMap} instances to see if they contain the same data + * or not. + * + * @param map1 {@link InputMethodMap} to be compared with + * @param map2 {@link InputMethodMap} to be compared with + * @return {@code true} if both {@link InputMethodMap} instances contain exactly the same data + */ + @AnyThread + static boolean areSame(@NonNull InputMethodMap map1, @NonNull InputMethodMap map2) { + if (map1 == map2) { + return true; + } + final int size = map1.size(); + if (size != map2.size()) { + return false; + } + for (int i = 0; i < size; ++i) { + final var imi1 = map1.valueAt(i); + final var imeId = imi1.getId(); + final var imi2 = map2.get(imeId); + if (imi2 == null) { + return false; + } + final var marshaled1 = InputMethodInfoUtils.marshal(imi1); + final var marshaled2 = InputMethodInfoUtils.marshal(imi2); + if (!Arrays.equals(marshaled1, marshaled2)) { + return false; + } + } + return true; + } } diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java index 7f00229395f8..2b19d3e0a2ea 100644 --- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java +++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java @@ -15,6 +15,7 @@ */ package com.android.server.inputmethod; + import android.annotation.NonNull; import android.annotation.UserIdInt; import android.content.pm.UserInfo; @@ -25,18 +26,21 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.pm.UserManagerInternal; import java.util.function.Consumer; +import java.util.function.IntFunction; final class UserDataRepository { @GuardedBy("ImfLock.class") private final SparseArray<UserData> mUserData = new SparseArray<>(); + private final IntFunction<InputMethodBindingController> mBindingControllerFactory; + @GuardedBy("ImfLock.class") @NonNull UserData getOrCreate(@UserIdInt int userId) { UserData userData = mUserData.get(userId); if (userData == null) { - userData = new UserData(userId); + userData = new UserData(userId, mBindingControllerFactory.apply(userId)); mUserData.put(userId, userData); } return userData; @@ -49,7 +53,9 @@ final class UserDataRepository { } } - UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal) { + UserDataRepository(@NonNull Handler handler, @NonNull UserManagerInternal userManagerInternal, + @NonNull IntFunction<InputMethodBindingController> bindingControllerFactory) { + mBindingControllerFactory = bindingControllerFactory; userManagerInternal.addUserLifecycleListener( new UserManagerInternal.UserLifecycleListener() { @Override @@ -79,11 +85,21 @@ final class UserDataRepository { @UserIdInt final int mUserId; - /** + @NonNull + final InputMethodBindingController mBindingController; + + /** * Intended to be instantiated only from this file. */ - private UserData(@UserIdInt int userId) { + private UserData(@UserIdInt int userId, + @NonNull InputMethodBindingController bindingController) { 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 573116105dea..b3fb147a318b 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -78,10 +78,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; +import java.util.PriorityQueue; +import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; /** @@ -152,6 +156,16 @@ public class ContextHubService extends IContextHubService.Stub { private final ScheduledThreadPoolExecutor mDailyMetricTimer = new ScheduledThreadPoolExecutor(1); + // A queue of reliable message records for duplicate detection + private final PriorityQueue<ReliableMessageRecord> mReliableMessageRecordQueue = + new PriorityQueue<ReliableMessageRecord>( + (ReliableMessageRecord left, ReliableMessageRecord right) -> { + return Long.compare(left.getTimestamp(), right.getTimestamp()); + }); + + // The test mode manager that manages behaviors during test mode. + private final TestModeManager mTestModeManager = new TestModeManager(); + // The period of the recurring time private static final int PERIOD_METRIC_QUERY_DAYS = 1; @@ -164,6 +178,9 @@ public class ContextHubService extends IContextHubService.Stub { private boolean mIsBtScanningEnabled = false; private boolean mIsBtMainEnabled = false; + // True if test mode is enabled for the Context Hub + private AtomicBoolean mIsTestModeEnabled = new AtomicBoolean(false); + // A hashmap used to record if a contexthub is waiting for daily query private Set<Integer> mMetricQueryPendingContextHubIds = Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>()); @@ -210,8 +227,17 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void handleNanoappMessage(short hostEndpointId, NanoAppMessage message, List<String> nanoappPermissions, List<String> messagePermissions) { - handleClientMessageCallback(mContextHubId, hostEndpointId, message, nanoappPermissions, - messagePermissions); + if (Flags.reliableMessageImplementation() + && Flags.reliableMessageTestModeBehavior() + && mIsTestModeEnabled.get() + && mTestModeManager.handleNanoappMessage(mContextHubId, hostEndpointId, + message, nanoappPermissions, messagePermissions)) { + // The TestModeManager handled the nanoapp message, so return here. + return; + } + + handleClientMessageCallback(mContextHubId, hostEndpointId, message, + nanoappPermissions, messagePermissions); } @Override @@ -228,6 +254,108 @@ public class ContextHubService extends IContextHubService.Stub { } } + /** + * Records a reliable message from a nanoapp for duplicate detection. + */ + private static class ReliableMessageRecord { + public static final int TIMEOUT_NS = 1000000000; + + public int mContextHubId; + public long mTimestamp; + public int mMessageSequenceNumber; + byte mErrorCode; + + ReliableMessageRecord(int contextHubId, long timestamp, + int messageSequenceNumber, byte errorCode) { + mContextHubId = contextHubId; + mTimestamp = timestamp; + mMessageSequenceNumber = messageSequenceNumber; + mErrorCode = errorCode; + } + + public int getContextHubId() { + return mContextHubId; + } + + public long getTimestamp() { + return mTimestamp; + } + + public int getMessageSequenceNumber() { + return mMessageSequenceNumber; + } + + public byte getErrorCode() { + return mErrorCode; + } + + public void setErrorCode(byte errorCode) { + mErrorCode = errorCode; + } + + public boolean isExpired() { + return mTimestamp + TIMEOUT_NS < SystemClock.elapsedRealtimeNanos(); + } + } + + /** + * A class to manage behaviors during test mode. This is used for testing. + */ + private class TestModeManager { + /** + * Probability (in percent) of duplicating a message. + */ + private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 50; + + /** + * The number of total messages to send when the duplicate event happens. + */ + private static final int NUM_MESSAGES_TO_DUPLICATE = 3; + + /** + * A probability percent for a certain event. + */ + private static final int MAX_PROBABILITY_PERCENT = 100; + + private Random mRandom = new Random(); + + /** + * @see ContextHubServiceCallback.handleNanoappMessage + * @return whether the message was handled + */ + public boolean handleNanoappMessage(int contextHubId, + short hostEndpointId, NanoAppMessage message, + List<String> nanoappPermissions, List<String> messagePermissions) { + if (!message.isReliable()) { + return false; + } + + 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); + } + return true; + } + return false; + } + + /** + * Returns true if the event with percentPercent did happen. + * + * @param probabilityPercent the percent probability of the event. + * @return true if the event happened, false otherwise. + */ + private boolean didEventHappen(int probabilityPercent) { + return mRandom.nextInt(MAX_PROBABILITY_PERCENT) < probabilityPercent; + } + } + public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) { Log.i(TAG, "Starting Context Hub Service init"); mContext = context; @@ -563,6 +691,8 @@ public class ContextHubService extends IContextHubService.Stub { * Resets the settings. Called when a context hub restarts or the AIDL HAL dies */ private void resetSettings() { + mIsTestModeEnabled.set(false); + sendLocationSettingUpdate(); sendWifiSettingUpdate(/* forceUpdate= */ true); sendAirplaneModeSettingUpdate(); @@ -854,14 +984,101 @@ public class ContextHubService extends IContextHubService.Stub { private void handleClientMessageCallback(int contextHubId, short hostEndpointId, NanoAppMessage message, List<String> nanoappPermissions, List<String> messagePermissions) { - byte errorCode = mClientManager.onMessageFromNanoApp(contextHubId, hostEndpointId, message, - nanoappPermissions, messagePermissions); - if (message.isReliable() && errorCode != ErrorCode.OK) { - sendMessageDeliveryStatusToContextHub(contextHubId, message.getMessageSequenceNumber(), - errorCode); + if (!Flags.reliableMessageImplementation() + || !Flags.reliableMessageDuplicateDetectionService()) { + byte errorCode = mClientManager.onMessageFromNanoApp(contextHubId, hostEndpointId, + message, nanoappPermissions, messagePermissions); + if (message.isReliable() && errorCode != ErrorCode.OK) { + sendMessageDeliveryStatusToContextHub(contextHubId, + message.getMessageSequenceNumber(), errorCode); + } + return; + } + + if (!message.isReliable()) { + mClientManager.onMessageFromNanoApp( + contextHubId, hostEndpointId, message, + nanoappPermissions, messagePermissions); + cleanupReliableMessageRecordQueue(); + return; + } + + byte errorCode = ErrorCode.OK; + synchronized (mReliableMessageRecordQueue) { + Optional<ReliableMessageRecord> record = + findReliableMessageRecord(contextHubId, + message.getMessageSequenceNumber()); + + if (record.isPresent()) { + errorCode = record.get().getErrorCode(); + if (errorCode == ErrorCode.TRANSIENT_ERROR) { + Log.w(TAG, "Found duplicate reliable message with message sequence number: " + + record.get().getMessageSequenceNumber() + ": retrying"); + errorCode = mClientManager.onMessageFromNanoApp( + contextHubId, hostEndpointId, message, + nanoappPermissions, messagePermissions); + record.get().setErrorCode(errorCode); + } else { + Log.w(TAG, "Found duplicate reliable message with message sequence number: " + + record.get().getMessageSequenceNumber()); + } + } else { + errorCode = mClientManager.onMessageFromNanoApp( + contextHubId, hostEndpointId, message, + nanoappPermissions, messagePermissions); + mReliableMessageRecordQueue.add( + new ReliableMessageRecord(contextHubId, + SystemClock.elapsedRealtimeNanos(), + message.getMessageSequenceNumber(), + errorCode)); + } + } + + sendMessageDeliveryStatusToContextHub(contextHubId, + message.getMessageSequenceNumber(), errorCode); + cleanupReliableMessageRecordQueue(); + } + + /** + * Finds a reliable message record in the queue that matches the given + * context hub ID and message sequence number. This function assumes + * the caller is synchronized on mReliableMessageRecordQueue. + * + * @param contextHubId the ID of the hub + * @param messageSequenceNumber the message sequence number + * + * @return the record if found, or empty if not found + */ + private Optional<ReliableMessageRecord> findReliableMessageRecord( + int contextHubId, int messageSequenceNumber) { + for (ReliableMessageRecord record: mReliableMessageRecordQueue) { + if (record.getContextHubId() == contextHubId + && record.getMessageSequenceNumber() == messageSequenceNumber) { + return Optional.of(record); + } } + return Optional.empty(); } + /** + * Removes old entries from the reliable message record queue. + */ + private void cleanupReliableMessageRecordQueue() { + synchronized (mReliableMessageRecordQueue) { + while (mReliableMessageRecordQueue.peek() != null + && mReliableMessageRecordQueue.peek().isExpired()) { + mReliableMessageRecordQueue.poll(); + } + } + } + + /** + * Sends the message delivery status to the Context Hub. + * + * @param contextHubId the ID of the hub + * @param messageSequenceNumber the message sequence number + * @param errorCode the error code, one of the enum ErrorCode + */ private void sendMessageDeliveryStatusToContextHub(int contextHubId, int messageSequenceNumber, byte errorCode) { if (!Flags.reliableMessageImplementation()) { @@ -1229,6 +1446,9 @@ public class ContextHubService extends IContextHubService.Stub { public boolean setTestMode(boolean enable) { super.setTestMode_enforcePermission(); boolean status = mContextHubWrapper.setTestMode(enable); + if (status) { + mIsTestModeEnabled.set(enable); + } // Query nanoapps to update service state after test mode state change. for (int contextHubId: mDefaultClientMap.keySet()) { diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java index a0dbfa082978..ec94e2be2c59 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java @@ -412,10 +412,15 @@ import java.util.concurrent.atomic.AtomicInteger; /* package */ synchronized void addTransaction( ContextHubServiceTransaction transaction) throws IllegalStateException { + if (transaction == null) { + return; + } + if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) { throw new IllegalStateException("Transaction queue is full (capacity = " + MAX_PENDING_REQUESTS + ")"); } + mTransactionQueue.add(transaction); mTransactionRecordDeque.add(new TransactionRecord(transaction.toString())); @@ -517,7 +522,10 @@ import java.util.concurrent.atomic.AtomicInteger; * the caller has obtained a lock on this ContextHubTransactionManager object. */ private void removeTransactionAndStartNext() { - mTimeoutFuture.cancel(false /* mayInterruptIfRunning */); + if (mTimeoutFuture != null) { + mTimeoutFuture.cancel(/* mayInterruptIfRunning= */ false); + mTimeoutFuture = null; + } ContextHubServiceTransaction transaction = mTransactionQueue.remove(); transaction.setComplete(); diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index dbdb155eb2e3..b14702dc6647 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -737,12 +737,13 @@ public class LockSettingsService extends ILockSettings.Stub { !mUserManager.isQuietModeEnabled(userHandle)) { // Only show notifications for managed profiles once their parent // user is unlocked. - showEncryptionNotificationForProfile(userHandle, reason); + showEncryptionNotificationForProfile(userHandle, parent.getUserHandle(), reason); } } } - private void showEncryptionNotificationForProfile(UserHandle user, String reason) { + private void showEncryptionNotificationForProfile(UserHandle user, UserHandle parent, + String reason) { CharSequence title = getEncryptionNotificationTitle(); CharSequence message = getEncryptionNotificationMessage(); CharSequence detail = getEncryptionNotificationDetail(); @@ -759,8 +760,15 @@ public class LockSettingsService extends ILockSettings.Stub { unlockIntent.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - PendingIntent intent = PendingIntent.getActivity(mContext, 0, unlockIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); + PendingIntent intent; + if (android.app.admin.flags.Flags.hsumUnlockNotificationFix()) { + intent = PendingIntent.getActivityAsUser(mContext, 0, unlockIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, + null, parent); + } else { + intent = PendingIntent.getActivity(mContext, 0, unlockIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); + } Slogf.d(TAG, "Showing encryption notification for user %d; reason: %s", user.getIdentifier(), reason); 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/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index a7fd750e4037..386657e99e36 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -81,6 +81,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider MediaRoute2ProviderServiceProxy( @NonNull Context context, + @NonNull Looper looper, @NonNull ComponentName componentName, boolean isSelfScanOnlyProvider, int userId) { @@ -88,7 +89,7 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider mContext = Objects.requireNonNull(context, "Context must not be null."); mIsSelfScanOnlyProvider = isSelfScanOnlyProvider; mUserId = userId; - mHandler = new Handler(Looper.myLooper()); + mHandler = new Handler(looper); } public void setManagerScanning(boolean managerScanning) { diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java index 178eb717f271..7c1a5e113a5e 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java @@ -142,6 +142,7 @@ final class MediaRoute2ProviderWatcher { MediaRoute2ProviderServiceProxy proxy = new MediaRoute2ProviderServiceProxy( mContext, + mHandler.getLooper(), new ComponentName(serviceInfo.packageName, serviceInfo.name), isSelfScanOnlyProvider, mUserId); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 869b89a6670c..c03497e629f0 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -404,37 +404,17 @@ class MediaRouter2ServiceImpl { long managerRequestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, - Bundle sessionHints, - @Nullable UserHandle transferInitiatorUserHandle, - @Nullable String transferInitiatorPackageName) { + Bundle sessionHints) { Objects.requireNonNull(router, "router must not be null"); Objects.requireNonNull(oldSession, "oldSession must not be null"); Objects.requireNonNull(route, "route must not be null"); - synchronized (mLock) { - if (managerRequestId == MediaRoute2ProviderService.REQUEST_ID_NONE - || transferInitiatorUserHandle == null - || transferInitiatorPackageName == null) { - final IBinder binder = router.asBinder(); - final RouterRecord routerRecord = mAllRouterRecords.get(binder); - - transferInitiatorUserHandle = Binder.getCallingUserHandle(); - if (routerRecord != null) { - transferInitiatorPackageName = routerRecord.mPackageName; - } else { - transferInitiatorPackageName = mContext.getPackageName(); - } - } - } - final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { requestCreateSessionWithRouter2Locked( requestId, managerRequestId, - transferInitiatorUserHandle, - transferInitiatorPackageName, router, oldSession, route, @@ -1281,8 +1261,6 @@ class MediaRouter2ServiceImpl { private void requestCreateSessionWithRouter2Locked( int requestId, long managerRequestId, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName, @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @@ -1305,22 +1283,20 @@ class MediaRouter2ServiceImpl { route.getId(), requestId)); + UserHandler userHandler = routerRecord.mUserRecord.mHandler; if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) { - ManagerRecord manager = routerRecord.mUserRecord.mHandler.findManagerWithId( - toRequesterId(managerRequestId)); + ManagerRecord manager = userHandler.findManagerWithId(toRequesterId(managerRequestId)); if (manager == null || manager.mLastSessionCreationRequest == null) { Slog.w(TAG, "requestCreateSessionWithRouter2Locked: " + "Ignoring unknown request."); - routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter( - routerRecord, requestId); + userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId); return; } if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(), oldSession.getId())) { Slog.w(TAG, "requestCreateSessionWithRouter2Locked: " + "Ignoring unmatched routing session."); - routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter( - routerRecord, requestId); + userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId); return; } if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(), @@ -1333,33 +1309,30 @@ class MediaRouter2ServiceImpl { } else { Slog.w(TAG, "requestCreateSessionWithRouter2Locked: " + "Ignoring unmatched route."); - routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter( - routerRecord, requestId); + userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId); return; } } manager.mLastSessionCreationRequest = null; } else { + String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId(); if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission() - && !TextUtils.equals(route.getId(), MediaRoute2Info.ROUTE_ID_DEFAULT)) { + && !TextUtils.equals(route.getId(), defaultRouteId)) { Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to" + route); - routerRecord.mUserRecord.mHandler.notifySessionCreationFailedToRouter( - routerRecord, requestId); + userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId); return; } } long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId); - routerRecord.mUserRecord.mHandler.sendMessage( + userHandler.sendMessage( obtainMessage( UserHandler::requestCreateSessionWithRouter2OnHandler, - routerRecord.mUserRecord.mHandler, + userHandler, uniqueRequestId, managerRequestId, - transferInitiatorUserHandle, - transferInitiatorPackageName, routerRecord, oldSession, route, @@ -1429,18 +1402,22 @@ class MediaRouter2ServiceImpl { "transferToRouteWithRouter2 | router: %s(id: %d), route: %s", routerRecord.mPackageName, routerRecord.mRouterId, route.getId())); + UserHandler userHandler = routerRecord.mUserRecord.mHandler; + String defaultRouteId = userHandler.mSystemProvider.getDefaultRoute().getId(); if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission() - && !TextUtils.equals(route.getId(), MediaRoute2Info.ROUTE_ID_DEFAULT)) { - routerRecord.mUserRecord.mHandler.sendMessage( - obtainMessage(UserHandler::notifySessionCreationFailedToRouter, - routerRecord.mUserRecord.mHandler, - routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID))); + && !TextUtils.equals(route.getId(), defaultRouteId)) { + userHandler.sendMessage( + obtainMessage( + UserHandler::notifySessionCreationFailedToRouter, + userHandler, + routerRecord, + toOriginalRequestId(DUMMY_REQUEST_ID))); } else { - routerRecord.mUserRecord.mHandler.sendMessage( + userHandler.sendMessage( obtainMessage( UserHandler::transferToRouteOnHandler, - routerRecord.mUserRecord.mHandler, + userHandler, DUMMY_REQUEST_ID, transferInitiatorUserHandle, routerRecord.mPackageName, @@ -2694,11 +2671,7 @@ class MediaRouter2ServiceImpl { route = mSystemProvider.getDefaultRoute(); } routerRecord.mRouter.requestCreateSessionByManager( - uniqueRequestId, - oldSession, - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + uniqueRequestId, oldSession, route); } catch (RemoteException ex) { Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: " + "Failed to request. Router probably died.", ex); @@ -2710,8 +2683,6 @@ class MediaRouter2ServiceImpl { private void requestCreateSessionWithRouter2OnHandler( long uniqueRequestId, long managerRequestId, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName, @NonNull RouterRecord routerRecord, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route, @@ -2731,10 +2702,10 @@ class MediaRouter2ServiceImpl { managerRequestId, oldSession, route); mSessionCreationRequests.add(request); - int transferReason = RoutingSessionInfo.TRANSFER_REASON_APP; - if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) { - transferReason = RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST; - } + int transferReason = + managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE + ? RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST + : RoutingSessionInfo.TRANSFER_REASON_APP; provider.requestCreateSession( uniqueRequestId, @@ -2742,8 +2713,8 @@ class MediaRouter2ServiceImpl { route.getOriginalId(), sessionHints, transferReason, - transferInitiatorUserHandle, - transferInitiatorPackageName); + UserHandle.of(routerRecord.mUserRecord.mUserId), + routerRecord.mPackageName); } // routerRecord can be null if the session is system's or RCN. @@ -2799,6 +2770,10 @@ class MediaRouter2ServiceImpl { final String providerId = route.getProviderId(); final MediaRoute2Provider provider = findProvider(providerId); if (provider == null) { + Slog.w( + TAG, + "Ignoring transferToRoute due to lack of matching provider for target: " + + route); return; } provider.transferToRoute( diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 064443ce7d10..192ac6287884 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -495,18 +495,9 @@ public final class MediaRouterService extends IMediaRouterService.Stub long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route, - Bundle sessionHints, - @Nullable UserHandle transferInitiatorUserHandle, - @Nullable String transferInitiatorPackageName) { + Bundle sessionHints) { mService2.requestCreateSessionWithRouter2( - router, - requestId, - managerRequestId, - oldSession, - route, - sessionHints, - transferInitiatorUserHandle, - transferInitiatorPackageName); + router, requestId, managerRequestId, oldSession, route, sessionHints); } // Binder call diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index a3c5d2d336f2..69f07d5c5f7b 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -1057,6 +1057,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return -1; } + @NonNull private PlaybackInfo getVolumeAttributes() { int volumeType; AudioAttributes attributes; @@ -1850,6 +1851,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde return mFlags; } + @NonNull @Override public PlaybackInfo getVolumeAttributes() { return MediaSessionRecord.this.getVolumeAttributes(); diff --git a/services/core/java/com/android/server/media/MediaShellCommand.java b/services/core/java/com/android/server/media/MediaShellCommand.java index a20de3198d2c..bea71dc5cedb 100644 --- a/services/core/java/com/android/server/media/MediaShellCommand.java +++ b/services/core/java/com/android/server/media/MediaShellCommand.java @@ -16,6 +16,7 @@ package com.android.server.media; +import android.annotation.NonNull; import android.app.ActivityThread; import android.content.Context; import android.media.MediaMetadata; @@ -247,7 +248,7 @@ public class MediaShellCommand extends ShellCommand { } @Override - public void onAudioInfoChanged(MediaController.PlaybackInfo info) { + public void onAudioInfoChanged(@NonNull MediaController.PlaybackInfo info) { mWriter.println("onAudioInfoChanged " + info); } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index c105b9c37026..6ce3ab4b2d65 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -232,10 +232,16 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { String sessionId, String routeId, @RoutingSessionInfo.TransferReason int transferReason) { + String selectedDeviceRouteId = mDeviceRouteController.getSelectedRoute().getId(); if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) { - // The currently selected route is the default route. - Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT); - return; + if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { + // Transfer to the default route (which is the selected route). We replace the id to + // be the selected route id so that the transfer reason gets updated. + routeId = selectedDeviceRouteId; + } else { + Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT); + return; + } } if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { @@ -250,11 +256,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } } - MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute(); + String finalRouteId = routeId; // Make a final copy to use it in the lambda. boolean isAvailableDeviceRoute = mDeviceRouteController.getAvailableRoutes().stream() - .anyMatch(it -> it.getId().equals(routeId)); - boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRoute.getId()); + .anyMatch(it -> it.getId().equals(finalRouteId)); + boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRouteId); if (isSelectedDeviceRoute || isAvailableDeviceRoute) { // The requested route is managed by the device route controller. Note that the selected diff --git a/services/core/java/com/android/server/net/NetworkManagementService.java b/services/core/java/com/android/server/net/NetworkManagementService.java index d25f52973085..5ea3e70f7957 100644 --- a/services/core/java/com/android/server/net/NetworkManagementService.java +++ b/services/core/java/com/android/server/net/NetworkManagementService.java @@ -20,6 +20,9 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -31,6 +34,9 @@ import static android.net.INetd.FIREWALL_RULE_DENY; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -143,6 +149,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub { private final Object mQuotaLock = new Object(); private final Object mRulesLock = new Object(); + private final boolean mUseMeteredFirewallChains; + /** Set of interfaces with active quotas. */ @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveQuotas = Maps.newHashMap(); @@ -150,9 +158,11 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @GuardedBy("mQuotaLock") private HashMap<String, Long> mActiveAlerts = Maps.newHashMap(); /** Set of UIDs denied on metered networks. */ + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mRulesLock") private SparseBooleanArray mUidRejectOnMetered = new SparseBooleanArray(); /** Set of UIDs allowed on metered networks. */ + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mRulesLock") private SparseBooleanArray mUidAllowOnMetered = new SparseBooleanArray(); /** Set of UIDs with cleartext penalties. */ @@ -196,10 +206,32 @@ public class NetworkManagementService extends INetworkManagementService.Stub { @GuardedBy("mRulesLock") private final SparseIntArray mUidFirewallBackgroundRules = new SparseIntArray(); + /** + * Contains the per-UID firewall rules that are used to allowlist the app from metered-network + * restrictions when data saver is enabled. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallAllowRules = new SparseIntArray(); + + /** + * Contains the per-UID firewall rules that are used to deny app access to metered networks + * due to user action. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallDenyUserRules = new SparseIntArray(); + + /** + * Contains the per-UID firewall rules that are used to deny app access to metered networks + * due to admin action. + */ + @GuardedBy("mRulesLock") + private final SparseIntArray mUidMeteredFirewallDenyAdminRules = new SparseIntArray(); + /** Set of states for the child firewall chains. True if the chain is active. */ @GuardedBy("mRulesLock") final SparseBooleanArray mFirewallChainStates = new SparseBooleanArray(); + // TODO: b/336693007 - Remove once NPMS has completely migrated to metered firewall chains. @GuardedBy("mQuotaLock") private volatile boolean mDataSaverMode; @@ -217,6 +249,15 @@ public class NetworkManagementService extends INetworkManagementService.Stub { mContext = context; mDeps = deps; + mUseMeteredFirewallChains = Flags.useMeteredFirewallChains(); + + if (mUseMeteredFirewallChains) { + // These firewalls are always on and currently ConnectivityService does not allow + // changing their enabled state. + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_USER, true); + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_DENY_ADMIN, true); + } + mDaemonHandler = new Handler(FgThread.get().getLooper()); mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); @@ -410,33 +451,39 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } } - SparseBooleanArray uidRejectOnQuota = null; - SparseBooleanArray uidAcceptOnQuota = null; - synchronized (mRulesLock) { - size = mUidRejectOnMetered.size(); - if (size > 0) { - if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules"); - uidRejectOnQuota = mUidRejectOnMetered; - mUidRejectOnMetered = new SparseBooleanArray(); - } + if (!mUseMeteredFirewallChains) { + SparseBooleanArray uidRejectOnQuota = null; + SparseBooleanArray uidAcceptOnQuota = null; + synchronized (mRulesLock) { + size = mUidRejectOnMetered.size(); + if (size > 0) { + if (DBG) { + Slog.d(TAG, "Pushing " + size + " UIDs to metered denylist rules"); + } + uidRejectOnQuota = mUidRejectOnMetered; + mUidRejectOnMetered = new SparseBooleanArray(); + } - size = mUidAllowOnMetered.size(); - if (size > 0) { - if (DBG) Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules"); - uidAcceptOnQuota = mUidAllowOnMetered; - mUidAllowOnMetered = new SparseBooleanArray(); + size = mUidAllowOnMetered.size(); + if (size > 0) { + if (DBG) { + Slog.d(TAG, "Pushing " + size + " UIDs to metered allowlist rules"); + } + uidAcceptOnQuota = mUidAllowOnMetered; + mUidAllowOnMetered = new SparseBooleanArray(); + } } - } - if (uidRejectOnQuota != null) { - for (int i = 0; i < uidRejectOnQuota.size(); i++) { - setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i), - uidRejectOnQuota.valueAt(i)); + if (uidRejectOnQuota != null) { + for (int i = 0; i < uidRejectOnQuota.size(); i++) { + setUidOnMeteredNetworkDenylist(uidRejectOnQuota.keyAt(i), + uidRejectOnQuota.valueAt(i)); + } } - } - if (uidAcceptOnQuota != null) { - for (int i = 0; i < uidAcceptOnQuota.size(); i++) { - setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i), - uidAcceptOnQuota.valueAt(i)); + if (uidAcceptOnQuota != null) { + for (int i = 0; i < uidAcceptOnQuota.size(); i++) { + setUidOnMeteredNetworkAllowlist(uidAcceptOnQuota.keyAt(i), + uidAcceptOnQuota.valueAt(i)); + } } } @@ -459,8 +506,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub { syncFirewallChainLocked(FIREWALL_CHAIN_RESTRICTED, "restricted "); syncFirewallChainLocked(FIREWALL_CHAIN_LOW_POWER_STANDBY, "low power standby "); syncFirewallChainLocked(FIREWALL_CHAIN_BACKGROUND, FIREWALL_CHAIN_NAME_BACKGROUND); + if (mUseMeteredFirewallChains) { + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_ALLOW, + FIREWALL_CHAIN_NAME_METERED_ALLOW); + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_USER, + FIREWALL_CHAIN_NAME_METERED_DENY_USER); + syncFirewallChainLocked(FIREWALL_CHAIN_METERED_DENY_ADMIN, + FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN); + } - final int[] chains = { + final int[] chainsToEnable = { FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE, @@ -469,14 +524,13 @@ public class NetworkManagementService extends INetworkManagementService.Stub { FIREWALL_CHAIN_BACKGROUND, }; - for (int chain : chains) { + for (int chain : chainsToEnable) { if (getFirewallChainState(chain)) { setFirewallChainEnabled(chain, true); } } } - try { getBatteryStats().noteNetworkStatsEnabled(); } catch (RemoteException e) { @@ -1077,6 +1131,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub { mContext.getSystemService(ConnectivityManager.class) .setDataSaverEnabled(enable); mDataSaverMode = enable; + if (mUseMeteredFirewallChains) { + // Copy mDataSaverMode state to FIREWALL_CHAIN_METERED_ALLOW + // until ConnectivityService allows manipulation of the data saver mode via + // FIREWALL_CHAIN_METERED_ALLOW. + synchronized (mRulesLock) { + mFirewallChainStates.put(FIREWALL_CHAIN_METERED_ALLOW, enable); + } + } return true; } else { final boolean changed = mNetdService.bandwidthEnableDataSaver(enable); @@ -1191,9 +1253,9 @@ public class NetworkManagementService extends INetworkManagementService.Stub { setFirewallChainState(chain, enable); } - final String chainName = getFirewallChainName(chain); - if (chain == FIREWALL_CHAIN_NONE) { - throw new IllegalArgumentException("Bad child chain: " + chainName); + if (!isValidFirewallChainForSetEnabled(chain)) { + throw new IllegalArgumentException("Invalid chain for setFirewallChainEnabled: " + + NetworkPolicyLogger.getFirewallChainName(chain)); } final ConnectivityManager cm = mContext.getSystemService(ConnectivityManager.class); @@ -1205,38 +1267,29 @@ public class NetworkManagementService extends INetworkManagementService.Stub { } } - private String getFirewallChainName(int chain) { - switch (chain) { - case FIREWALL_CHAIN_STANDBY: - return FIREWALL_CHAIN_NAME_STANDBY; - case FIREWALL_CHAIN_DOZABLE: - return FIREWALL_CHAIN_NAME_DOZABLE; - case FIREWALL_CHAIN_POWERSAVE: - return FIREWALL_CHAIN_NAME_POWERSAVE; - case FIREWALL_CHAIN_RESTRICTED: - return FIREWALL_CHAIN_NAME_RESTRICTED; - case FIREWALL_CHAIN_LOW_POWER_STANDBY: - return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; - case FIREWALL_CHAIN_BACKGROUND: - return FIREWALL_CHAIN_NAME_BACKGROUND; - default: - throw new IllegalArgumentException("Bad child chain: " + chain); - } + private boolean isValidFirewallChainForSetEnabled(int chain) { + return switch (chain) { + case FIREWALL_CHAIN_STANDBY, FIREWALL_CHAIN_DOZABLE, FIREWALL_CHAIN_POWERSAVE, + FIREWALL_CHAIN_RESTRICTED, FIREWALL_CHAIN_LOW_POWER_STANDBY, + FIREWALL_CHAIN_BACKGROUND -> true; + // METERED_* firewall chains are not yet supported by + // ConnectivityService#setFirewallChainEnabled. + default -> false; + }; } private int getFirewallType(int chain) { switch (chain) { case FIREWALL_CHAIN_STANDBY: + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + case FIREWALL_CHAIN_METERED_DENY_USER: return FIREWALL_DENYLIST; case FIREWALL_CHAIN_DOZABLE: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_POWERSAVE: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_RESTRICTED: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_LOW_POWER_STANDBY: - return FIREWALL_ALLOWLIST; case FIREWALL_CHAIN_BACKGROUND: + case FIREWALL_CHAIN_METERED_ALLOW: return FIREWALL_ALLOWLIST; default: return isFirewallEnabled() ? FIREWALL_ALLOWLIST : FIREWALL_DENYLIST; @@ -1360,6 +1413,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub { return mUidFirewallLowPowerStandbyRules; case FIREWALL_CHAIN_BACKGROUND: return mUidFirewallBackgroundRules; + case FIREWALL_CHAIN_METERED_ALLOW: + return mUidMeteredFirewallAllowRules; + case FIREWALL_CHAIN_METERED_DENY_USER: + return mUidMeteredFirewallDenyUserRules; + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + return mUidMeteredFirewallDenyAdminRules; case FIREWALL_CHAIN_NONE: return mUidFirewallRules; default: @@ -1378,6 +1437,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; + pw.println("Flags:"); + pw.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + mUseMeteredFirewallChains); + pw.println(); + synchronized (mQuotaLock) { pw.print("Active quota ifaces: "); pw.println(mActiveQuotas.toString()); pw.print("Active alert ifaces: "); pw.println(mActiveAlerts.toString()); @@ -1416,6 +1479,27 @@ public class NetworkManagementService extends INetworkManagementService.Stub { pw.print("UID firewall background chain enabled: "); pw.println(getFirewallChainState(FIREWALL_CHAIN_BACKGROUND)); dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_BACKGROUND, mUidFirewallBackgroundRules); + + pw.print("UID firewall metered allow chain enabled (Data saver mode): "); + // getFirewallChainState should maintain a duplicated state from mDataSaverMode when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_ALLOW, + mUidMeteredFirewallAllowRules); + + pw.print("UID firewall metered deny_user chain enabled (always-on): "); + // This always-on state should be reflected by getFirewallChainState when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_USER, + mUidMeteredFirewallDenyUserRules); + + pw.print("UID firewall metered deny_admin chain enabled (always-on): "); + // This always-on state should be reflected by getFirewallChainState when + // mUseMeteredFirewallChains is enabled. + pw.println(getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN)); + dumpUidFirewallRule(pw, FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN, + mUidMeteredFirewallDenyAdminRules); } pw.print("Firewall enabled: "); pw.println(mFirewallEnabled); @@ -1520,14 +1604,40 @@ public class NetworkManagementService extends INetworkManagementService.Stub { if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because it is in background"); return true; } - if (mUidRejectOnMetered.get(uid)) { - if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of no metered data" - + " in the background"); - return true; - } - if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) { - if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); - return true; + if (mUseMeteredFirewallChains) { + if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_USER) + && mUidMeteredFirewallDenyUserRules.get(uid) == FIREWALL_RULE_DENY) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + " restricted because of user-restricted metered" + + " data in the background"); + } + return true; + } + if (getFirewallChainState(FIREWALL_CHAIN_METERED_DENY_ADMIN) + && mUidMeteredFirewallDenyAdminRules.get(uid) == FIREWALL_RULE_DENY) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + " restricted because of admin-restricted metered" + + " data in the background"); + } + return true; + } + if (getFirewallChainState(FIREWALL_CHAIN_METERED_ALLOW) + && mUidMeteredFirewallAllowRules.get(uid) != FIREWALL_RULE_ALLOW) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); + return true; + } + } else { + if (mUidRejectOnMetered.get(uid)) { + if (DBG) { + Slog.d(TAG, "Uid " + uid + + " restricted because of no metered data in the background"); + } + return true; + } + if (mDataSaverMode && !mUidAllowOnMetered.get(uid)) { + if (DBG) Slog.d(TAG, "Uid " + uid + " restricted because of data saver mode"); + return true; + } } return false; } diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 8e2d7780204a..681aa8aef219 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -19,6 +19,9 @@ import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -28,6 +31,9 @@ import static android.net.NetworkPolicyManager.ALLOWED_REASON_NONE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_BACKGROUND; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_ALLOW; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; +import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_METERED_DENY_USER; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_RESTRICTED; import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY; @@ -379,7 +385,7 @@ public class NetworkPolicyLogger { return "Interfaces of netId=" + netId + " changed to " + newIfaces; } - private static String getFirewallChainName(int chain) { + static String getFirewallChainName(int chain) { switch (chain) { case FIREWALL_CHAIN_DOZABLE: return FIREWALL_CHAIN_NAME_DOZABLE; @@ -393,6 +399,12 @@ public class NetworkPolicyLogger { return FIREWALL_CHAIN_NAME_LOW_POWER_STANDBY; case FIREWALL_CHAIN_BACKGROUND: return FIREWALL_CHAIN_NAME_BACKGROUND; + case FIREWALL_CHAIN_METERED_ALLOW: + return FIREWALL_CHAIN_NAME_METERED_ALLOW; + case FIREWALL_CHAIN_METERED_DENY_USER: + return FIREWALL_CHAIN_NAME_METERED_DENY_USER; + case FIREWALL_CHAIN_METERED_DENY_ADMIN: + return FIREWALL_CHAIN_NAME_METERED_DENY_ADMIN; default: return String.valueOf(chain); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 22f5332e150c..c60ac3a74ebd 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -60,6 +60,9 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; @@ -514,6 +517,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { */ private boolean mBackgroundNetworkRestricted; + /** + * Whether or not metered firewall chains should be used for uid policy controlling access to + * metered networks. + */ + private boolean mUseMeteredFirewallChains; + // See main javadoc for instructions on how to use these locks. final Object mUidRulesFirstLock = new Object(); final Object mNetworkPoliciesSecondLock = new Object(); @@ -997,6 +1006,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mAppStandby = LocalServices.getService(AppStandbyInternal.class); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); + mUseMeteredFirewallChains = Flags.useMeteredFirewallChains(); + synchronized (mUidRulesFirstLock) { synchronized (mNetworkPoliciesSecondLock) { updatePowerSaveAllowlistUL(); @@ -4030,8 +4041,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.println(); fout.println("Flags:"); - fout.println("Network blocked for TOP_SLEEPING and above: " + fout.println(Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE + ": " + mBackgroundNetworkRestricted); + fout.println(Flags.FLAG_USE_METERED_FIREWALL_CHAINS + ": " + + mUseMeteredFirewallChains); fout.println(); fout.println("mRestrictBackgroundLowPowerMode: " + mRestrictBackgroundLowPowerMode); @@ -5373,23 +5386,44 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { postUidRulesChangedMsg(uid, uidRules); } - // Note that the conditionals below are for avoiding unnecessary calls to netd. - // TODO: Measure the performance for doing a no-op call to netd so that we can - // remove the conditionals to simplify the logic below. We can also further reduce - // some calls to netd if they turn out to be costly. - final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED - | BLOCKED_METERED_REASON_USER_RESTRICTED; - if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE - || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) { - setMeteredNetworkDenylist(uid, - (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE); - } - final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND - | ALLOWED_METERED_REASON_USER_EXEMPTED; - if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE - || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) { - setMeteredNetworkAllowlist(uid, - (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE); + if (mUseMeteredFirewallChains) { + if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_ADMIN_DISABLED) + != BLOCKED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DENY); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, FIREWALL_RULE_DEFAULT); + } + if ((newEffectiveBlockedReasons & BLOCKED_METERED_REASON_USER_RESTRICTED) + != BLOCKED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DENY); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_DENY_USER, uid, FIREWALL_RULE_DEFAULT); + } + if ((newAllowedReasons & (ALLOWED_METERED_REASON_FOREGROUND + | ALLOWED_METERED_REASON_USER_EXEMPTED)) != ALLOWED_REASON_NONE) { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_ALLOW); + } else { + setUidFirewallRuleUL(FIREWALL_CHAIN_METERED_ALLOW, uid, FIREWALL_RULE_DEFAULT); + } + } else { + // Note that the conditionals below are for avoiding unnecessary calls to netd. + // TODO: Measure the performance for doing a no-op call to netd so that we can + // remove the conditionals to simplify the logic below. We can also further reduce + // some calls to netd if they turn out to be costly. + final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED + | BLOCKED_METERED_REASON_USER_RESTRICTED; + if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE + || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) { + setMeteredNetworkDenylist(uid, + (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE); + } + final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND + | ALLOWED_METERED_REASON_USER_EXEMPTED; + if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE + || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) { + setMeteredNetworkAllowlist(uid, + (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE); + } } } @@ -6149,6 +6183,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } else if (chain == FIREWALL_CHAIN_BACKGROUND) { mUidFirewallBackgroundRules.put(uid, rule); } + // Note that we do not need keep a separate cache of uid rules for chains that we do + // not call #setUidFirewallRulesUL for. try { mNetworkManager.setFirewallUidRule(chain, uid, rule); @@ -6206,10 +6242,19 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { FIREWALL_RULE_DEFAULT); mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_BACKGROUND, uid, FIREWALL_RULE_DEFAULT); - mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false); - mLogger.meteredAllowlistChanged(uid, false); - mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false); - mLogger.meteredDenylistChanged(uid, false); + if (mUseMeteredFirewallChains) { + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, uid, + FIREWALL_RULE_DEFAULT); + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, uid, + FIREWALL_RULE_DEFAULT); + mNetworkManager.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, uid, + FIREWALL_RULE_DEFAULT); + } else { + mNetworkManager.setUidOnMeteredNetworkAllowlist(uid, false); + mLogger.meteredAllowlistChanged(uid, false); + mNetworkManager.setUidOnMeteredNetworkDenylist(uid, false); + mLogger.meteredDenylistChanged(uid, false); + } } catch (IllegalStateException e) { Log.wtf(TAG, "problem resetting firewall uid rules for " + uid, e); } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/net/flags.aconfig b/services/core/java/com/android/server/net/flags.aconfig index d9491de52d87..e986dd81b94b 100644 --- a/services/core/java/com/android/server/net/flags.aconfig +++ b/services/core/java/com/android/server/net/flags.aconfig @@ -7,3 +7,13 @@ flag { description: "Block network access for apps in a low importance background state" bug: "304347838" } + +flag { + name: "use_metered_firewall_chains" + namespace: "backstage_power" + description: "Use metered firewall chains to control access to metered networks" + bug: "336693007" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index babb6c219714..13cc99c804f6 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -108,16 +108,25 @@ public class GroupHelper { return (flags & mask) != 0; } - public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) { + /** + * Called when a notification is newly posted. Checks whether that notification, and all other + * active notifications should be grouped or ungrouped atuomatically, and returns whether. + * @param sbn The posted notification. + * @param autogroupSummaryExists Whether a summary for this notification already exists. + * @return Whether the provided notification should be autogrouped synchronously. + */ + public boolean onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) { + boolean sbnToBeAutogrouped = false; try { if (!sbn.isAppGroup()) { - maybeGroup(sbn, autogroupSummaryExists); + sbnToBeAutogrouped = maybeGroup(sbn, autogroupSummaryExists); } else { maybeUngroup(sbn, false, sbn.getUserId()); } } catch (Exception e) { Slog.e(TAG, "Failure processing new notification", e); } + return sbnToBeAutogrouped; } public void onNotificationRemoved(StatusBarNotification sbn) { @@ -137,20 +146,22 @@ public class GroupHelper { * * And stores the list of upgrouped notifications & their flags */ - private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) { + private boolean maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) { int flags = 0; List<String> notificationsToGroup = new ArrayList<>(); List<NotificationAttributes> childrenAttr = new ArrayList<>(); + // Indicates whether the provided sbn should be autogrouped by the caller. + boolean sbnToBeAutogrouped = false; synchronized (mUngroupedNotifications) { - String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName()); + String packageKey = generatePackageKey(sbn.getUserId(), sbn.getPackageName()); final ArrayMap<String, NotificationAttributes> children = - mUngroupedNotifications.getOrDefault(key, new ArrayMap<>()); + mUngroupedNotifications.getOrDefault(packageKey, new ArrayMap<>()); NotificationAttributes attr = new NotificationAttributes(sbn.getNotification().flags, sbn.getNotification().getSmallIcon(), sbn.getNotification().color, sbn.getNotification().visibility); children.put(sbn.getKey(), attr); - mUngroupedNotifications.put(key, children); + mUngroupedNotifications.put(packageKey, children); if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) { flags = getAutogroupSummaryFlags(children); @@ -187,10 +198,20 @@ public class GroupHelper { mCallback.addAutoGroupSummary(sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), attr); } - for (String key : notificationsToGroup) { - mCallback.addAutoGroup(key); + for (String keyToGroup : notificationsToGroup) { + if (android.app.Flags.checkAutogroupBeforePost()) { + if (keyToGroup.equals(sbn.getKey())) { + // Autogrouping for the provided notification is to be done synchronously. + sbnToBeAutogrouped = true; + } else { + mCallback.addAutoGroup(keyToGroup, /*requestSort=*/true); + } + } else { + mCallback.addAutoGroup(keyToGroup, /*requestSort=*/true); + } } } + return sbnToBeAutogrouped; } /** @@ -406,7 +427,7 @@ public class GroupHelper { } protected interface Callback { - void addAutoGroup(String key); + void addAutoGroup(String key, boolean requestSort); void removeAutoGroup(String key); void addAutoGroupSummary(int userId, String pkg, String triggeringKey, diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java index bf49671e2d82..13429db47ebd 100644 --- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java +++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java @@ -22,12 +22,14 @@ import static android.app.Notification.FLAG_ONLY_ALERT_ONCE; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; import static android.media.audio.Flags.focusExclusiveWithRecording; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS; +import android.Manifest.permission; import android.annotation.IntDef; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -135,7 +137,7 @@ public final class NotificationAttentionHelper { private LogicalLight mAttentionLight; private final boolean mUseAttentionLight; - boolean mHasLight = true; + boolean mHasLight; private final SettingsObserver mSettingsObserver; @@ -149,7 +151,7 @@ public final class NotificationAttentionHelper { private boolean mInCallStateOffHook = false; private boolean mScreenOn = true; private boolean mUserPresent = false; - boolean mNotificationPulseEnabled; + private boolean mNotificationPulseEnabled; private final Uri mInCallNotificationUri; private final AudioAttributes mInCallNotificationAudioAttributes; private final float mInCallNotificationVolume; @@ -223,7 +225,10 @@ public final class NotificationAttentionHelper { mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), - mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET), + record -> mPackageManager.checkPermission( + permission.RECEIVE_EMERGENCY_BROADCAST, + record.getSbn().getPackageName()) == PERMISSION_GRANTED); return new StrategyAvalanche( mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), @@ -231,14 +236,17 @@ public final class NotificationAttentionHelper { mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_AVALANCHE_TIMEOUT), - appStrategy); + appStrategy, appStrategy.mExemptionProvider); } else { return new StrategyPerApp( mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_T2), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME1), mFlagResolver.getIntValue(NotificationFlags.NOTIF_VOLUME2), - mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET)); + mFlagResolver.getIntValue(NotificationFlags.NOTIF_COOLDOWN_COUNTER_RESET), + record -> mPackageManager.checkPermission( + permission.RECEIVE_EMERGENCY_BROADCAST, + record.getSbn().getPackageName()) == PERMISSION_GRANTED); } } @@ -305,6 +313,13 @@ public final class NotificationAttentionHelper { } private void loadUserSettings() { + boolean pulseEnabled = Settings.System.getIntForUser(mContext.getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE, 0, UserHandle.USER_CURRENT) != 0; + if (mNotificationPulseEnabled != pulseEnabled) { + mNotificationPulseEnabled = pulseEnabled; + updateLightsLocked(); + } + if (Flags.politeNotifications()) { try { mCurrentWorkProfileId = getManagedProfileId(ActivityManager.getCurrentUser()); @@ -874,6 +889,9 @@ public final class NotificationAttentionHelper { boolean canShowLightsLocked(final NotificationRecord record, final Signals signals, boolean aboveThreshold) { + if (!mSystemReady) { + return false; + } // device lacks light if (!mHasLight) { return false; @@ -1088,6 +1106,11 @@ public final class NotificationAttentionHelper { } } + // Returns true if a notification should be exempted from attenuation + private interface ExemptionProvider { + boolean isExempted(NotificationRecord record); + } + @VisibleForTesting abstract static class PolitenessStrategy { static final int POLITE_STATE_DEFAULT = 0; @@ -1118,8 +1141,10 @@ public final class NotificationAttentionHelper { protected boolean mIsActive = true; + protected final ExemptionProvider mExemptionProvider; + public PolitenessStrategy(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted) { + int volumeMuted, ExemptionProvider exemptionProvider) { mVolumeStates = new HashMap<>(); mLastUpdatedTimestampByPackage = new HashMap<>(); @@ -1127,6 +1152,7 @@ public final class NotificationAttentionHelper { this.mTimeoutMuted = timeoutMuted; this.mVolumePolite = volumePolite / 100.0f; this.mVolumeMuted = volumeMuted / 100.0f; + this.mExemptionProvider = exemptionProvider; } abstract void onNotificationPosted(NotificationRecord record); @@ -1284,8 +1310,8 @@ public final class NotificationAttentionHelper { private final int mMaxPostedForReset; public StrategyPerApp(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted, int maxPosted) { - super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + int volumeMuted, int maxPosted, ExemptionProvider exemptionProvider) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider); mNumPosted = new HashMap<>(); mMaxPostedForReset = maxPosted; @@ -1306,7 +1332,12 @@ public final class NotificationAttentionHelper { final String key = getChannelKey(record); @PolitenessState final int currState = getPolitenessState(record); - @PolitenessState int nextState = getNextState(currState, timeSinceLastNotif); + @PolitenessState int nextState; + if (Flags.politeNotificationsAttnUpdate()) { + nextState = getNextState(currState, timeSinceLastNotif, record); + } else { + nextState = getNextState(currState, timeSinceLastNotif); + } // Reset to default state if number of posted notifications exceed this value when muted int numPosted = mNumPosted.getOrDefault(key, 0) + 1; @@ -1324,6 +1355,14 @@ public final class NotificationAttentionHelper { mVolumeStates.put(key, nextState); } + @PolitenessState int getNextState(@PolitenessState final int currState, + final long timeSinceLastNotif, final NotificationRecord record) { + if (mExemptionProvider.isExempted(record)) { + return POLITE_STATE_DEFAULT; + } + return getNextState(currState, timeSinceLastNotif); + } + @Override public void onUserInteraction(final NotificationRecord record) { super.onUserInteraction(record); @@ -1344,8 +1383,9 @@ public final class NotificationAttentionHelper { private long mLastAvalancheTriggerTimestamp = 0; StrategyAvalanche(int timeoutPolite, int timeoutMuted, int volumePolite, - int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy) { - super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted); + int volumeMuted, int timeoutAvalanche, PolitenessStrategy appStrategy, + ExemptionProvider exemptionProvider) { + super(timeoutPolite, timeoutMuted, volumePolite, volumeMuted, exemptionProvider); mTimeoutAvalanche = timeoutAvalanche; mAppStrategy = appStrategy; @@ -1518,7 +1558,7 @@ public final class NotificationAttentionHelper { return true; } - return false; + return mExemptionProvider.isExempted(record); } private boolean isAvalancheExempted(final NotificationRecord record) { @@ -1721,8 +1761,6 @@ public final class NotificationAttentionHelper { void setLights(LogicalLight light) { mNotificationLight = light; mAttentionLight = light; - mNotificationPulseEnabled = true; - mHasLight = true; } @VisibleForTesting diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index ca6ae63e74fc..44e76941c09b 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -245,7 +245,6 @@ import android.os.DeadObjectException; import android.os.DeviceIdleManager; import android.os.Environment; import android.os.Handler; -import android.os.HandlerExecutor; import android.os.HandlerThread; import android.os.IBinder; import android.os.IInterface; @@ -594,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; @@ -2038,19 +2039,21 @@ public class NotificationManagerService extends SystemService { mSnoozeHelper.clearData(userHandle); } } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); - mUserProfiles.updateCache(context); - if (!mUserProfiles.isProfileUser(userId, context)) { - // reload per-user settings - mSettingsObserver.update(null); - // Refresh managed services - mConditionProviders.onUserSwitched(userId); - mListeners.onUserSwitched(userId); - mZenModeHelper.onUserSwitched(userId); - mPreferencesHelper.syncChannelsBypassingDnd(); - } - // assistant is the only thing that cares about managed profiles specifically - mAssistants.onUserSwitched(userId); + if (!Flags.useSsmUserSwitchSignal()) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); + mUserProfiles.updateCache(context); + if (!mUserProfiles.isProfileUser(userId, context)) { + // reload per-user settings + mSettingsObserver.update(null); + // Refresh managed services + mConditionProviders.onUserSwitched(userId); + mListeners.onUserSwitched(userId); + mZenModeHelper.onUserSwitched(userId); + mPreferencesHelper.syncChannelsBypassingDnd(); + } + // assistant is the only thing that cares about managed profiles specifically + mAssistants.onUserSwitched(userId); + } } else if (action.equals(Intent.ACTION_USER_ADDED)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); if (userId != USER_NULL) { @@ -2570,7 +2573,9 @@ public class NotificationManagerService extends SystemService { // calling onDestroy() IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_STOPPED); - filter.addAction(Intent.ACTION_USER_SWITCHED); + if (!Flags.useSsmUserSwitchSignal()) { + filter.addAction(Intent.ACTION_USER_SWITCHED); + } filter.addAction(Intent.ACTION_USER_ADDED); filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_UNLOCKED); @@ -2634,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) { @@ -2793,10 +2819,10 @@ public class NotificationManagerService extends SystemService { return new GroupHelper(getContext(), getContext().getPackageManager(), mAutoGroupAtCount, new GroupHelper.Callback() { @Override - public void addAutoGroup(String key) { - synchronized (mNotificationLock) { - addAutogroupKeyLocked(key); - } + public void addAutoGroup(String key, boolean requestSort) { + synchronized (mNotificationLock) { + addAutogroupKeyLocked(key, requestSort); + } } @Override @@ -2966,6 +2992,26 @@ public class NotificationManagerService extends SystemService { } @Override + public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { + if (!Flags.useSsmUserSwitchSignal()) { + return; + } + final int userId = to.getUserIdentifier(); + mUserProfiles.updateCache(getContext()); + if (!mUserProfiles.isProfileUser(userId, getContext())) { + // reload per-user settings + mSettingsObserver.update(null); + // Refresh managed services + mConditionProviders.onUserSwitched(userId); + mListeners.onUserSwitched(userId); + mZenModeHelper.onUserSwitched(userId); + mPreferencesHelper.syncChannelsBypassingDnd(); + } + // assistant is the only thing that cares about managed profiles specifically + mAssistants.onUserSwitched(userId); + } + + @Override public void onUserStopping(@NonNull TargetUser user) { mHandler.post(() -> { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryStopUser"); @@ -5786,7 +5832,16 @@ public class NotificationManagerService extends SystemService { @Override public ComponentName getEffectsSuppressor() { - return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null; + ComponentName suppressor = !mEffectsSuppressors.isEmpty() + ? mEffectsSuppressors.get(0) + : null; + if (isCallerSystemOrSystemUiOrShell() || suppressor == null + || mPackageManagerInternal.isSameApp(suppressor.getPackageName(), + Binder.getCallingUid(), UserHandle.getUserId(Binder.getCallingUid()))) { + return suppressor; + } + + return null; } @Override @@ -6538,7 +6593,7 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") - void addAutogroupKeyLocked(String key) { + void addAutogroupKeyLocked(String key, boolean requestSort) { NotificationRecord r = mNotificationsByKey.get(key); if (r == null) { return; @@ -6546,7 +6601,9 @@ public class NotificationManagerService extends SystemService { if (r.getSbn().getOverrideGroupKey() == null) { addAutoGroupAdjustment(r, GroupHelper.AUTOGROUP_KEY); EventLogTags.writeNotificationAutogrouped(key); - mRankingHandler.requestSort(); + if (!android.app.Flags.checkAutogroupBeforePost() || requestSort) { + mRankingHandler.requestSort(); + } } } @@ -7177,7 +7234,15 @@ public class NotificationManagerService extends SystemService { callingUid, userId, true, false, "cancelNotificationWithTag", pkg); // ensure opPkg is delegate if does not match pkg - int uid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + + int uid = INVALID_UID; + + try { + uid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + } catch (NameNotFoundException e) { + // package either never existed so there's no posted notification or it's being + // uninstalled so we'll be cleaning it up soon. log and return immediately below. + } if (uid == INVALID_UID) { Slog.w(TAG, opPkg + ":" + callingUid + " trying to cancel notification " @@ -7271,7 +7336,13 @@ public class NotificationManagerService extends SystemService { // Can throw a SecurityException if the calling uid doesn't have permission to post // as "pkg" - final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + int notificationUid = INVALID_UID; + + try { + notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId); + } catch (NameNotFoundException e) { + // not great - throw immediately below + } if (notificationUid == INVALID_UID) { throw new SecurityException("Caller " + opPkg + ":" + callingUid @@ -7697,6 +7768,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; @@ -7825,7 +7899,8 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting - int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) { + int resolveNotificationUid(String callingPkg, String targetPkg, int callingUid, int userId) + throws NameNotFoundException { if (userId == USER_ALL) { userId = USER_SYSTEM; } @@ -7836,12 +7911,8 @@ public class NotificationManagerService extends SystemService { return callingUid; } - int targetUid = INVALID_UID; - try { - targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId); - } catch (NameNotFoundException e) { - /* ignore, handled by caller */ - } + int targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId); + // posted from app A on behalf of app B if (isCallerAndroid(callingPkg, callingUid) || mPreferencesHelper.isDelegateAllowed( @@ -7991,6 +8062,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; } @@ -8609,6 +8687,29 @@ public class NotificationManagerService extends SystemService { notification.flags |= FLAG_NO_CLEAR; } + // Posts the notification if it has a small icon, and potentially autogroup + // the new notification. + if (android.app.Flags.checkAutogroupBeforePost()) { + if (notification.getSmallIcon() != null && !isCritical(r)) { + StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; + if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()) + || oldSbn.getNotification().flags + != n.getNotification().flags) { + synchronized (mNotificationLock) { + boolean willBeAutogrouped = mGroupHelper.onNotificationPosted(n, + hasAutoGroupSummaryLocked(n)); + if (willBeAutogrouped) { + // The newly posted notification will be autogrouped, but + // was not autogrouped onPost, to avoid an unnecessary sort. + // We add the autogroup key to the notification without a + // sort here, and it'll be sorted below with extractSignals. + addAutogroupKeyLocked(key, /*requestSort=*/false); + } + } + } + } + } + mRankingHelper.extractSignals(r); mRankingHelper.sort(mNotificationList); final int position = mRankingHelper.indexOf(mNotificationList, r); @@ -8629,17 +8730,20 @@ public class NotificationManagerService extends SystemService { notifyListenersPostedAndLogLocked(r, old, mTracker, maybeReport); posted = true; - StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; - if (oldSbn == null - || !Objects.equals(oldSbn.getGroup(), n.getGroup()) - || oldSbn.getNotification().flags != n.getNotification().flags) { - if (!isCritical(r)) { - mHandler.post(() -> { - synchronized (mNotificationLock) { - mGroupHelper.onNotificationPosted( - n, hasAutoGroupSummaryLocked(n)); - } - }); + if (!android.app.Flags.checkAutogroupBeforePost()) { + StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; + if (oldSbn == null + || !Objects.equals(oldSbn.getGroup(), n.getGroup()) + || oldSbn.getNotification().flags + != n.getNotification().flags) { + if (!isCritical(r)) { + mHandler.post(() -> { + synchronized (mNotificationLock) { + mGroupHelper.onNotificationPosted( + n, hasAutoGroupSummaryLocked(n)); + } + }); + } } } } else { @@ -11220,6 +11324,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/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 143bc5cb20ff..b589f4972155 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -486,6 +486,7 @@ public class ZenModeHelper { } } + @GuardedBy("mConfigLock") private ZenRule maybeRestoreRemovedRule(ZenModeConfig config, ZenRule ruleToAdd, AutomaticZenRule azrToAdd, @ConfigChangeOrigin int origin) { if (!Flags.modesApi()) { @@ -1112,6 +1113,7 @@ public class ZenModeHelper { * <p>The rule's {@link ZenRule#condition} is cleared (meaning that an active rule will be * deactivated) unless the update has origin == {@link ZenModeConfig#UPDATE_ORIGIN_USER}. */ + @GuardedBy("mConfigLock") private boolean populateZenRule(String pkg, AutomaticZenRule azr, ZenRule rule, @ConfigChangeOrigin int origin, boolean isNew) { if (Flags.modesApi()) { @@ -1261,12 +1263,14 @@ public class ZenModeHelper { * * <p>Returns {@code true} if the policy of the rule was modified. */ + @GuardedBy("mConfigLock") private boolean updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy, boolean updateBitmask, boolean isNew) { if (newPolicy == null) { if (isNew) { // Newly created rule with no provided policy; fill in with the default. - zenRule.zenPolicy = mDefaultConfig.toZenPolicy(); + zenRule.zenPolicy = + Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy(); return true; } // Otherwise, a null policy means no policy changes, so we can stop here. @@ -1275,8 +1279,9 @@ public class ZenModeHelper { // If oldPolicy is null, we compare against the default policy when determining which // fields in the bitmask should be marked as updated. - ZenPolicy oldPolicy = - zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy(); + ZenPolicy oldPolicy = zenRule.zenPolicy != null + ? zenRule.zenPolicy + : (Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy()); // If this is updating a rule rather than creating a new one, keep any fields from the // old policy if they are unspecified in the new policy. For newly created rules, oldPolicy @@ -2033,7 +2038,8 @@ public class ZenModeHelper { // rule's policy fields should be set upon creation, this is a fallback to // catch any that may have fallen through the cracks. Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule); - policy.apply(mDefaultConfig.toZenPolicy()); + policy.apply( + Flags.modesUi() ? mDefaultConfig.toZenPolicy() : mConfig.toZenPolicy()); } } else { // active rule with no specified policy inherits the global config settings diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index af3db6c6be4f..bf6b6521c19a 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -128,3 +128,17 @@ flag { description: "Adds an IPCDataCache for notification channel/group lookups" bug: "331677193" } + +flag { + name: "use_ssm_user_switch_signal" + namespace: "systemui" + description: "This flag controls which signal is used to handle a user switch system event" + 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/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 6c93fe787816..56e459057bfa 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -89,6 +89,7 @@ import com.android.server.LocalServices; import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.pm.KnownPackages; +import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; import com.android.server.pm.pkg.PackageState; @@ -289,6 +290,9 @@ public final class OverlayManagerService extends SystemService { getContext().registerReceiverAsUser(new UserReceiver(), UserHandle.ALL, userFilter, null, null); + UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); + umi.addUserLifecycleListener(new UserLifecycleListener()); + restoreSettings(); // Wipe all shell overlays on boot, to recover from a potentially broken device @@ -339,6 +343,7 @@ public final class OverlayManagerService extends SystemService { if (newUserId == mPrevStartedUserId) { return; } + Slog.i(TAG, "Updating overlays for starting user " + newUserId); try { traceBegin(TRACE_TAG_RRO, "OMS#onStartUser " + newUserId); // ensure overlays in the settings are up-to-date, and propagate @@ -515,14 +520,46 @@ public final class OverlayManagerService extends SystemService { } } + /** + * Indicates that the given user is of great importance so that when it is created, we quickly + * update its overlays by using a Listener mechanism rather than a Broadcast mechanism. This + * is especially important for {@link UserManager#isHeadlessSystemUserMode() HSUM}'s MainUser, + * which is created and switched-to immediately on first boot. + */ + private static boolean isHighPriorityUserCreation(UserInfo user) { + // TODO: Consider extending this to all created users (guarded behind a flag in that case). + return user != null && user.isMain(); + } + + private final class UserLifecycleListener implements UserManagerInternal.UserLifecycleListener { + @Override + public void onUserCreated(UserInfo user, Object token) { + if (isHighPriorityUserCreation(user)) { + final int userId = user.id; + try { + Slog.i(TAG, "Updating overlays for onUserCreated " + userId); + traceBegin(TRACE_TAG_RRO, "OMS#onUserCreated " + userId); + synchronized (mLock) { + updatePackageManagerLocked(mImpl.updateOverlaysForUser(userId)); + } + } finally { + traceEnd(TRACE_TAG_RRO); + } + } + } + } + private final class UserReceiver extends BroadcastReceiver { @Override public void onReceive(@NonNull final Context context, @NonNull final Intent intent) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); switch (intent.getAction()) { case ACTION_USER_ADDED: - if (userId != UserHandle.USER_NULL) { + UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class); + UserInfo userInfo = umi.getUserInfo(userId); + if (userId != UserHandle.USER_NULL && !isHighPriorityUserCreation(userInfo)) { try { + Slog.i(TAG, "Updating overlays for added user " + userId); traceBegin(TRACE_TAG_RRO, "OMS ACTION_USER_ADDED"); synchronized (mLock) { updatePackageManagerLocked(mImpl.updateOverlaysForUser(userId)); diff --git a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java index 681dd0b49f4e..96ab2ccc8611 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java +++ b/services/core/java/com/android/server/ondeviceintelligence/BundleUtil.java @@ -33,6 +33,7 @@ import android.graphics.Bitmap; import android.os.BadParcelableException; import android.os.Bundle; import android.os.ParcelFileDescriptor; +import android.os.Parcelable; import android.os.PersistableBundle; import android.os.RemoteCallback; import android.os.RemoteException; @@ -41,7 +42,10 @@ import android.system.ErrnoException; import android.system.Os; import android.util.Log; +import com.android.internal.infra.AndroidFuture; + import java.util.concurrent.Executor; +import java.util.concurrent.TimeoutException; /** * Util methods for ensuring the Bundle passed in various methods are read-only and restricted to @@ -78,16 +82,16 @@ public class BundleUtil { if (canMarshall(obj) || obj instanceof CursorWindow) { continue; } - - if (obj instanceof ParcelFileDescriptor) { + if (obj instanceof Bundle) { + sanitizeInferenceParams((Bundle) obj); + } else if (obj instanceof ParcelFileDescriptor) { validatePfdReadOnly((ParcelFileDescriptor) obj); } else if (obj instanceof SharedMemory) { ((SharedMemory) obj).setProtect(PROT_READ); } else if (obj instanceof Bitmap) { - if (((Bitmap) obj).isMutable()) { - throw new BadParcelableException( - "Encountered a mutable Bitmap in the Bundle at key : " + key); - } + validateBitmap((Bitmap) obj); + } else if (obj instanceof Parcelable[]) { + validateParcelableArray((Parcelable[]) obj); } else { throw new BadParcelableException( "Unsupported Parcelable type encountered in the Bundle: " @@ -125,20 +129,20 @@ public class BundleUtil { continue; } - if (obj instanceof ParcelFileDescriptor) { + if (obj instanceof Bundle) { + sanitizeResponseParams((Bundle) obj); + } else if (obj instanceof ParcelFileDescriptor) { validatePfdReadOnly((ParcelFileDescriptor) obj); } else if (obj instanceof Bitmap) { - if (((Bitmap) obj).isMutable()) { - throw new BadParcelableException( - "Encountered a mutable Bitmap in the Bundle at key : " + key); - } + validateBitmap((Bitmap) obj); + } else if (obj instanceof Parcelable[]) { + validateParcelableArray((Parcelable[]) obj); } else { throw new BadParcelableException( "Unsupported Parcelable type encountered in the Bundle: " + obj.getClass().getSimpleName()); } } - Log.e(TAG, "validateResponseParams : Finished"); } /** @@ -183,7 +187,8 @@ public class BundleUtil { public static IStreamingResponseCallback wrapWithValidation( IStreamingResponseCallback streamingResponseCallback, - Executor resourceClosingExecutor) { + Executor resourceClosingExecutor, + AndroidFuture future) { return new IStreamingResponseCallback.Stub() { @Override public void onNewContent(Bundle processedResult) throws RemoteException { @@ -203,6 +208,7 @@ public class BundleUtil { streamingResponseCallback.onSuccess(resultBundle); } finally { resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle)); + future.complete(null); } } @@ -210,6 +216,7 @@ public class BundleUtil { public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) throws RemoteException { streamingResponseCallback.onFailure(errorCode, errorMessage, errorParams); + future.completeExceptionally(new TimeoutException()); } @Override @@ -237,7 +244,8 @@ public class BundleUtil { } public static IResponseCallback wrapWithValidation(IResponseCallback responseCallback, - Executor resourceClosingExecutor) { + Executor resourceClosingExecutor, + AndroidFuture future) { return new IResponseCallback.Stub() { @Override public void onSuccess(Bundle resultBundle) @@ -247,6 +255,7 @@ public class BundleUtil { responseCallback.onSuccess(resultBundle); } finally { resourceClosingExecutor.execute(() -> tryCloseResource(resultBundle)); + future.complete(null); } } @@ -254,6 +263,7 @@ public class BundleUtil { public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) throws RemoteException { responseCallback.onFailure(errorCode, errorMessage, errorParams); + future.completeExceptionally(new TimeoutException()); } @Override @@ -280,17 +290,20 @@ public class BundleUtil { } - public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback) { + public static ITokenInfoCallback wrapWithValidation(ITokenInfoCallback responseCallback, + AndroidFuture future) { return new ITokenInfoCallback.Stub() { @Override public void onSuccess(TokenInfo tokenInfo) throws RemoteException { responseCallback.onSuccess(tokenInfo); + future.complete(null); } @Override public void onFailure(int errorCode, String errorMessage, PersistableBundle errorParams) throws RemoteException { responseCallback.onFailure(errorCode, errorMessage, errorParams); + future.completeExceptionally(new TimeoutException()); } }; } @@ -310,6 +323,26 @@ public class BundleUtil { } } + private static void validateParcelableArray(Parcelable[] parcelables) { + if (parcelables.length > 0 + && parcelables[0] instanceof ParcelFileDescriptor) { + // Safe to cast + validatePfdsReadOnly(parcelables); + } else if (parcelables.length > 0 + && parcelables[0] instanceof Bitmap) { + validateBitmapsImmutable(parcelables); + } else { + throw new BadParcelableException( + "Could not cast to any known parcelable array"); + } + } + + public static void validatePfdsReadOnly(Parcelable[] pfds) { + for (Parcelable pfd : pfds) { + validatePfdReadOnly((ParcelFileDescriptor) pfd); + } + } + public static void validatePfdReadOnly(ParcelFileDescriptor pfd) { if (pfd == null) { return; @@ -326,6 +359,19 @@ public class BundleUtil { } } + private static void validateBitmap(Bitmap obj) { + if (obj.isMutable()) { + throw new BadParcelableException( + "Encountered a mutable Bitmap in the Bundle at key : " + obj); + } + } + + private static void validateBitmapsImmutable(Parcelable[] bitmaps) { + for (Parcelable bitmap : bitmaps) { + validateBitmap((Bitmap) bitmap); + } + } + public static void tryCloseResource(Bundle bundle) { if (bundle == null || bundle.isEmpty() || !bundle.hasFileDescriptors()) { return; diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java index 99401a17f83c..dd7603714718 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -16,6 +16,11 @@ package com.android.server.ondeviceintelligence; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.DEVICE_CONFIG_UPDATE_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_LOADED_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.MODEL_UNLOADED_BUNDLE_KEY; +import static android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService.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; @@ -29,6 +34,7 @@ import android.annotation.RequiresPermission; import android.app.AppGlobals; import android.app.ondeviceintelligence.DownloadCallback; import android.app.ondeviceintelligence.Feature; +import android.app.ondeviceintelligence.FeatureDetails; import android.app.ondeviceintelligence.IDownloadCallback; import android.app.ondeviceintelligence.IFeatureCallback; import android.app.ondeviceintelligence.IFeatureDetailsCallback; @@ -41,6 +47,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; @@ -59,6 +66,7 @@ import android.os.ResultReceiver; import android.os.ShellCallback; import android.os.UserHandle; import android.provider.DeviceConfig; +import android.provider.Settings; import android.service.ondeviceintelligence.IOnDeviceIntelligenceService; import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService; import android.service.ondeviceintelligence.IProcessingUpdateStatusCallback; @@ -77,13 +85,17 @@ import com.android.internal.infra.ServiceConnector; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.ondeviceintelligence.callbacks.ListenableDownloadCallback; import java.io.FileDescriptor; import java.io.IOException; +import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * This is the system service for handling calls on the @@ -104,13 +116,23 @@ 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; + /** Handler message to clean up temporary config namespace. */ + private static final int MSG_RESET_CONFIG_NAMESPACE = 2; /** 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 Executor mConfigExecutor = Executors.newCachedThreadPool(); + private final Context mContext; protected final Object mLock = new Object(); @@ -122,12 +144,23 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @GuardedBy("mLock") private String[] mTemporaryServiceNames; + @GuardedBy("mLock") + private String[] mTemporaryBroadcastKeys; + @GuardedBy("mLock") + private String mBroadcastPackageName; + @GuardedBy("mLock") + private String mTemporaryConfigNamespace; + + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = + this::sendUpdatedConfig; + /** * Handler used to reset the temporary service names. */ - @GuardedBy("mLock") private Handler mTemporaryHandler; + private final @NonNull Handler mMainHandler = new Handler(Looper.getMainLooper()); + public OnDeviceIntelligenceManagerService(Context context) { super(context); @@ -187,8 +220,16 @@ public class OnDeviceIntelligenceManagerService extends SystemService { return; } ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.getVersion(remoteCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.getVersion(new RemoteCallback( + result -> { + remoteCallback.sendResult(result); + future.complete(null); + })); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); } @Override @@ -208,8 +249,25 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.getFeature(callerUid, id, featureCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.getFeature(callerUid, id, new IFeatureCallback.Stub() { + @Override + public void onSuccess(Feature result) throws RemoteException { + featureCallback.onSuccess(result); + future.complete(null); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) throws RemoteException { + featureCallback.onFailure(errorCode, errorMessage, errorParams); + future.completeExceptionally(new TimeoutException()); + } + }); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); } @Override @@ -229,9 +287,29 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.listFeatures(callerUid, - listFeaturesCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.listFeatures(callerUid, + new IListFeaturesCallback.Stub() { + @Override + public void onSuccess(List<Feature> result) + throws RemoteException { + listFeaturesCallback.onSuccess(result); + future.complete(null); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) + throws RemoteException { + listFeaturesCallback.onFailure(errorCode, errorMessage, + errorParams); + future.completeExceptionally(new TimeoutException()); + } + }); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); } @Override @@ -253,9 +331,29 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.getFeatureDetails(callerUid, feature, - featureDetailsCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.getFeatureDetails(callerUid, feature, + new IFeatureDetailsCallback.Stub() { + @Override + public void onSuccess(FeatureDetails result) + throws RemoteException { + future.complete(null); + featureDetailsCallback.onSuccess(result); + } + + @Override + public void onFailure(int errorCode, String errorMessage, + PersistableBundle errorParams) + throws RemoteException { + future.completeExceptionally(null); + featureDetailsCallback.onFailure(errorCode, + errorMessage, errorParams); + } + }); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); } @Override @@ -276,10 +374,20 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteIntelligenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - mRemoteOnDeviceIntelligenceService.run( - service -> service.requestFeatureDownload(callerUid, feature, - wrapCancellationFuture(cancellationSignalFuture), - downloadCallback)); + mRemoteOnDeviceIntelligenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + ListenableDownloadCallback listenableDownloadCallback = + new ListenableDownloadCallback( + downloadCallback, + mMainHandler, future, getIdleTimeoutMs()); + service.requestFeatureDownload(callerUid, feature, + wrapCancellationFuture(cancellationSignalFuture), + listenableDownloadCallback); + return future; // this future has no timeout because, actual download + // might take long, but we fail early if there is no progress callbacks. + } + ); } @@ -306,11 +414,15 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteInferenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - result = mRemoteInferenceService.post( - service -> service.requestTokenInfo(callerUid, feature, - request, - wrapCancellationFuture(cancellationSignalFuture), - wrapWithValidation(tokenInfoCallback))); + result = mRemoteInferenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.requestTokenInfo(callerUid, feature, + request, + wrapCancellationFuture(cancellationSignalFuture), + wrapWithValidation(tokenInfoCallback, future)); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request), resourceClosingExecutor); } finally { @@ -345,13 +457,18 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteInferenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - result = mRemoteInferenceService.post( - service -> service.processRequest(callerUid, feature, - request, - requestType, - wrapCancellationFuture(cancellationSignalFuture), - wrapProcessingFuture(processingSignalFuture), - wrapWithValidation(responseCallback, resourceClosingExecutor))); + result = mRemoteInferenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.processRequest(callerUid, feature, + request, + requestType, + wrapCancellationFuture(cancellationSignalFuture), + wrapProcessingFuture(processingSignalFuture), + wrapWithValidation(responseCallback, + resourceClosingExecutor, future)); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request), resourceClosingExecutor); } finally { @@ -385,13 +502,18 @@ public class OnDeviceIntelligenceManagerService extends SystemService { } ensureRemoteInferenceServiceInitialized(); int callerUid = Binder.getCallingUid(); - result = mRemoteInferenceService.post( - service -> service.processRequestStreaming(callerUid, - feature, - request, requestType, - wrapCancellationFuture(cancellationSignalFuture), - wrapProcessingFuture(processingSignalFuture), - streamingCallback)); + result = mRemoteInferenceService.postAsync( + service -> { + AndroidFuture future = new AndroidFuture(); + service.processRequestStreaming(callerUid, + feature, + request, requestType, + wrapCancellationFuture(cancellationSignalFuture), + wrapProcessingFuture(processingSignalFuture), + wrapWithValidation(streamingCallback, + resourceClosingExecutor, future)); + return future.orTimeout(getIdleTimeoutMs(), TimeUnit.MILLISECONDS); + }); result.whenCompleteAsync((c, e) -> BundleUtil.tryCloseResource(request), resourceClosingExecutor); } finally { @@ -480,10 +602,14 @@ public class OnDeviceIntelligenceManagerService extends SystemService { @NonNull IOnDeviceSandboxedInferenceService service) { try { ensureRemoteIntelligenceServiceInitialized(); - mRemoteOnDeviceIntelligenceService.run( - IOnDeviceIntelligenceService::notifyInferenceServiceConnected); service.registerRemoteStorageService( getIRemoteStorageService()); + mRemoteOnDeviceIntelligenceService.run( + IOnDeviceIntelligenceService::notifyInferenceServiceConnected); + broadcastExecutor.execute( + () -> registerModelLoadingBroadcasts(service)); + mConfigExecutor.execute( + () -> registerDeviceConfigChangeListener()); } catch (RemoteException ex) { Slog.w(TAG, "Failed to send connected event", ex); } @@ -493,6 +619,106 @@ 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); + } + } + + private void registerDeviceConfigChangeListener() { + Log.d(TAG, "registerDeviceConfigChangeListener"); + String configNamespace = getConfigNamespace(); + if (configNamespace.isEmpty()) { + Slog.e(TAG, "config_defaultOnDeviceIntelligenceDeviceConfigNamespace is empty"); + return; + } + DeviceConfig.addOnPropertiesChangedListener( + configNamespace, + mConfigExecutor, + mOnPropertiesChangedListener); + } + + private String getConfigNamespace() { + synchronized (mLock) { + if (mTemporaryConfigNamespace != null) { + return mTemporaryConfigNamespace; + } + + return mContext.getResources().getString( + R.string.config_defaultOnDeviceIntelligenceDeviceConfigNamespace); + } + } + + private void sendUpdatedConfig( + DeviceConfig.Properties props) { + Log.d(TAG, "sendUpdatedConfig"); + + PersistableBundle persistableBundle = new PersistableBundle(); + for (String key : props.getKeyset()) { + persistableBundle.putString(key, props.getString(key, "")); + } + Bundle bundle = new Bundle(); + bundle.putParcelable(DEVICE_CONFIG_UPDATE_BUNDLE_KEY, persistableBundle); + ensureRemoteInferenceServiceInitialized(); + mRemoteInferenceService.run(service -> service.updateProcessingState(bundle, + new IProcessingUpdateStatusCallback.Stub() { + @Override + public void onSuccess(PersistableBundle result) { + Slog.d(TAG, "Config update successful." + result); + } + + @Override + public void onFailure(int errorCode, String errorMessage) { + Slog.e(TAG, "Config update failed with code [" + + String.valueOf(errorCode) + "] and message = " + errorMessage); + } + })); + } + @NonNull private IRemoteStorageService.Stub getIRemoteStorageService() { return new IRemoteStorageService.Stub() { @@ -629,6 +855,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,32 +885,48 @@ 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) { + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_BROADCAST_KEYS, durationMs); + } + } + } + + @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) + public void setTemporaryDeviceConfigNamespace(@NonNull String configNamespace, + int durationMs) { + Objects.requireNonNull(configNamespace); + enforceShellOnly(Binder.getCallingUid(), "setTemporaryDeviceConfigNamespace"); + mContext.enforceCallingPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + synchronized (mLock) { + mTemporaryConfigNamespace = configNamespace; if (durationMs != -1) { - mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs); + getTemporaryHandler().sendEmptyMessageDelayed(MSG_RESET_CONFIG_NAMESPACE, + durationMs); } } } @RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void resetTemporaryServices() { - enforceShellOnly(Binder.getCallingUid(), "resetTemporaryServices"); mContext.enforceCallingPermission( Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); synchronized (mLock) { @@ -751,4 +1007,34 @@ 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) { + synchronized (mLock) { + if (msg.what == MSG_RESET_TEMPORARY_SERVICE) { + resetTemporaryServices(); + } else if (msg.what == MSG_RESET_BROADCAST_KEYS) { + mTemporaryBroadcastKeys = null; + mBroadcastPackageName = SYSTEM_PACKAGE; + } else if (msg.what == MSG_RESET_CONFIG_NAMESPACE) { + mTemporaryConfigNamespace = null; + } else { + Slog.wtf(TAG, "invalid handler msg: " + msg); + } + } + } + }; + } + + return mTemporaryHandler; + } + + private long getIdleTimeoutMs() { + return Settings.Secure.getLongForUser(mContext.getContentResolver(), + Settings.Secure.ON_DEVICE_INTELLIGENCE_IDLE_TIMEOUT_MS, TimeUnit.HOURS.toMillis(1), + mContext.getUserId()); + } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java index a76d8a31405d..d2c84fa1b18a 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceShellCommand.java @@ -17,6 +17,7 @@ package com.android.server.ondeviceintelligence; import android.annotation.NonNull; +import android.os.Binder; import android.os.ShellCommand; import java.io.PrintWriter; @@ -43,6 +44,10 @@ final class OnDeviceIntelligenceShellCommand extends ShellCommand { return setTemporaryServices(); case "get-services": return getConfiguredServices(); + case "set-model-broadcasts": + return setBroadcastKeys(); + case "set-deviceconfig-namespace": + return setDeviceConfigNamespace(); default: return handleDefaultCommands(cmd); } @@ -62,14 +67,26 @@ 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."); + pw.println( + " set-deviceconfig-namespace [DeviceConfigNamespace] " + + "[DURATION] To set the device config namespace " + + "to use 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) { + OnDeviceIntelligenceManagerService.enforceShellOnly(Binder.getCallingUid(), + "resetTemporaryServices"); mService.resetTemporaryServices(); out.println("OnDeviceIntelligenceManagerService temporary reset. "); return 0; @@ -79,7 +96,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 +111,34 @@ 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; + } + + private int setDeviceConfigNamespace() { + final PrintWriter out = getOutPrintWriter(); + final String configNamespace = getNextArg(); + + final int duration = Integer.parseInt(getNextArgRequired()); + mService.setTemporaryDeviceConfigNamespace(configNamespace, duration); + out.println("OnDeviceIntelligence DeviceConfig Namespace temporarily set to " + + configNamespace + + " for " + duration + "ms"); + return 0; + } + }
\ No newline at end of file diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java index 48258d7bea72..ac9747aa83b3 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java @@ -22,17 +22,21 @@ import static android.content.Context.BIND_INCLUDE_CAPABILITIES; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.provider.Settings; import android.service.ondeviceintelligence.IOnDeviceIntelligenceService; import android.service.ondeviceintelligence.OnDeviceIntelligenceService; import com.android.internal.infra.ServiceConnector; +import java.util.concurrent.TimeUnit; + /** * Manages the connection to the remote on-device intelligence service. Also, handles unbinding * logic set by the service implementation via a Secure Settings flag. */ public class RemoteOnDeviceIntelligenceService extends ServiceConnector.Impl<IOnDeviceIntelligenceService> { + private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(4); private static final String TAG = RemoteOnDeviceIntelligenceService.class.getSimpleName(); @@ -48,9 +52,15 @@ public class RemoteOnDeviceIntelligenceService extends } @Override + protected long getRequestTimeoutMs() { + return LONG_TIMEOUT; + } + + @Override protected long getAutoDisconnectTimeoutMs() { - // Disable automatic unbinding. - // TODO: add logic to fetch this flag via SecureSettings. - return -1; + return Settings.Secure.getLongForUser(mContext.getContentResolver(), + Settings.Secure.ON_DEVICE_INTELLIGENCE_UNBIND_TIMEOUT_MS, + TimeUnit.SECONDS.toMillis(30), + mContext.getUserId()); } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java index 69ba1d2fb599..18b13838ea7c 100644 --- a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java +++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceSandboxedInferenceService.java @@ -22,18 +22,24 @@ import static android.content.Context.BIND_INCLUDE_CAPABILITIES; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.provider.Settings; import android.service.ondeviceintelligence.IOnDeviceSandboxedInferenceService; import android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService; import com.android.internal.infra.ServiceConnector; +import java.util.concurrent.TimeUnit; + /** - * Manages the connection to the remote on-device sand boxed inference service. Also, handles unbinding + * Manages the connection to the remote on-device sand boxed inference service. Also, handles + * unbinding * logic set by the service implementation via a SecureSettings flag. */ public class RemoteOnDeviceSandboxedInferenceService extends ServiceConnector.Impl<IOnDeviceSandboxedInferenceService> { + private static final long LONG_TIMEOUT = TimeUnit.HOURS.toMillis(1); + /** * Creates an instance of {@link ServiceConnector} * @@ -54,11 +60,17 @@ public class RemoteOnDeviceSandboxedInferenceService extends connect(); } + @Override + protected long getRequestTimeoutMs() { + return LONG_TIMEOUT; + } + @Override protected long getAutoDisconnectTimeoutMs() { - // Disable automatic unbinding. - // TODO: add logic to fetch this flag via SecureSettings. - return -1; + return Settings.Secure.getLongForUser(mContext.getContentResolver(), + Settings.Secure.ON_DEVICE_INFERENCE_UNBIND_TIMEOUT_MS, + TimeUnit.SECONDS.toMillis(30), + mContext.getUserId()); } } diff --git a/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java new file mode 100644 index 000000000000..32f0698a8f9c --- /dev/null +++ b/services/core/java/com/android/server/ondeviceintelligence/callbacks/ListenableDownloadCallback.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.ondeviceintelligence.callbacks; + +import android.app.ondeviceintelligence.IDownloadCallback; +import android.os.Handler; +import android.os.PersistableBundle; +import android.os.RemoteException; + +import com.android.internal.infra.AndroidFuture; + +import java.util.concurrent.TimeoutException; + +/** + * This class extends the {@link IDownloadCallback} and adds a timeout Runnable to the callback + * such that, in the case where the callback methods are not invoked, we do not have to wait for + * timeout based on {@link #onDownloadCompleted} which might take minutes or hours to complete in + * some cases. Instead, in such cases we rely on the remote service sending progress updates and if + * there are *no* progress callbacks in the duration of {@link #idleTimeoutMs}, we can assume the + * download will not complete and enabling faster cleanup. + */ +public class ListenableDownloadCallback extends IDownloadCallback.Stub implements Runnable { + private final IDownloadCallback callback; + private final Handler handler; + private final AndroidFuture future; + private final long idleTimeoutMs; + + /** + * Constructor to create a ListenableDownloadCallback. + * + * @param callback callback to send download updates to caller. + * @param handler handler to schedule timeout runnable. + * @param future future to complete to signal the callback has reached a terminal state. + * @param idleTimeoutMs timeout within which download updates should be received. + */ + public ListenableDownloadCallback(IDownloadCallback callback, Handler handler, + AndroidFuture future, + long idleTimeoutMs) { + this.callback = callback; + this.handler = handler; + this.future = future; + this.idleTimeoutMs = idleTimeoutMs; + handler.postDelayed(this, + idleTimeoutMs); // init the timeout runnable in case no callback is ever invoked + } + + @Override + public void onDownloadStarted(long bytesToDownload) throws RemoteException { + callback.onDownloadStarted(bytesToDownload); + handler.removeCallbacks(this); + handler.postDelayed(this, idleTimeoutMs); + } + + @Override + public void onDownloadProgress(long bytesDownloaded) throws RemoteException { + callback.onDownloadProgress(bytesDownloaded); + handler.removeCallbacks(this); // remove previously queued timeout tasks. + handler.postDelayed(this, idleTimeoutMs); // queue fresh timeout task for next update. + } + + @Override + public void onDownloadFailed(int failureStatus, + String errorMessage, PersistableBundle errorParams) throws RemoteException { + callback.onDownloadFailed(failureStatus, errorMessage, errorParams); + handler.removeCallbacks(this); + future.completeExceptionally(new TimeoutException()); + } + + @Override + public void onDownloadCompleted( + android.os.PersistableBundle downloadParams) throws RemoteException { + callback.onDownloadCompleted(downloadParams); + handler.removeCallbacks(this); + future.complete(null); + } + + @Override + public void run() { + future.completeExceptionally( + new TimeoutException()); // complete the future as we haven't received updates + // for download progress. + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 8a853287738b..f07b7106c0d4 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -17,6 +17,7 @@ package com.android.server.os; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; +import static android.app.admin.flags.Flags.onboardingConsentlessBugreports; import android.Manifest; import android.annotation.NonNull; @@ -31,6 +32,7 @@ import android.content.pm.UserInfo; import android.os.Binder; import android.os.BugreportManager.BugreportCallback; import android.os.BugreportParams; +import android.os.Build; import android.os.Environment; import android.os.IDumpstate; import android.os.IDumpstateListener; @@ -46,6 +48,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; import android.util.LocalLog; +import android.util.MutableBoolean; import android.util.Pair; import android.util.Slog; import android.util.Xml; @@ -68,12 +71,14 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.OptionalInt; import java.util.Set; +import java.util.concurrent.TimeUnit; /** * Implementation of the service that provides a privileged API to capture and consume bugreports. @@ -97,6 +102,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private static final String BUGREPORT_SERVICE = "bugreportd"; private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000; + private static final long DEFAULT_BUGREPORT_CONSENTLESS_GRACE_PERIOD_MILLIS = + TimeUnit.MINUTES.toMillis(2); + private final Object mLock = new Object(); private final Injector mInjector; private final Context mContext; @@ -104,6 +112,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private final TelephonyManager mTelephonyManager; private final ArraySet<String> mBugreportAllowlistedPackages; private final BugreportFileManager mBugreportFileManager; + private static final FeatureFlags sFeatureFlags = new FeatureFlagsImpl(); @GuardedBy("mLock") @@ -130,6 +139,10 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles = new ArrayMap<>(); + // Map of <CallerPackage, Pair<TimestampOfLastConsent, skipConsentForFullReport>> + @GuardedBy("mLock") + private Map<String, Pair<Long, Boolean>> mConsentGranted = new HashMap<>(); + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @GuardedBy("mLock") final Set<String> mBugreportFilesToPersist = new HashSet<>(); @@ -236,6 +249,64 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } } + /** + * Logs an entry with a timestamp of a consent being granted by the user to the calling + * {@code packageName}. + */ + void logConsentGrantedForCaller( + String packageName, boolean consentGranted, boolean isDeferredReport) { + if (!onboardingConsentlessBugreports() || !Build.IS_DEBUGGABLE) { + return; + } + synchronized (mLock) { + // Adds an entry with the timestamp of the consent being granted by the user, and + // whether the consent can be skipped for a full bugreport, because a single + // consent can be used for multiple deferred reports but only one full report. + if (consentGranted) { + mConsentGranted.put(packageName, new Pair<>( + System.currentTimeMillis(), + isDeferredReport)); + } else if (!isDeferredReport) { + if (!mConsentGranted.containsKey(packageName)) { + Slog.e(TAG, "Previous consent from package: " + packageName + " should" + + "have been logged."); + return; + } + mConsentGranted.put(packageName, new Pair<>( + mConsentGranted.get(packageName).first, + /* second = */ false + )); + } + } + } + + /** + * Returns {@code true} if user consent be skippeb because a previous consens has been + * granted to the caller within the allowed time period. + */ + boolean canSkipConsentScreen(String packageName, boolean isFullReport) { + if (!onboardingConsentlessBugreports() || !Build.IS_DEBUGGABLE) { + return false; + } + synchronized (mLock) { + if (!mConsentGranted.containsKey(packageName)) { + return false; + } + long currentTime = System.currentTimeMillis(); + long consentGrantedTime = mConsentGranted.get(packageName).first; + if (consentGrantedTime + DEFAULT_BUGREPORT_CONSENTLESS_GRACE_PERIOD_MILLIS + < currentTime) { + mConsentGranted.remove(packageName); + return false; + } + boolean skipConsentForFullReport = mConsentGranted.get(packageName).second; + if (isFullReport && !skipConsentForFullReport) { + return false; + } + return true; + } + } + private void addBugreportMapping(Pair<Integer, String> caller, String bugreportFile) { synchronized (mLock) { if (!mBugreportFiles.containsKey(caller)) { @@ -416,7 +487,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { public void startBugreport(int callingUidUnused, String callingPackage, FileDescriptor bugreportFd, FileDescriptor screenshotFd, int bugreportMode, int bugreportFlags, IDumpstateListener listener, - boolean isScreenshotRequested) { + boolean isScreenshotRequested, boolean skipUserConsentUnused) { Objects.requireNonNull(callingPackage); Objects.requireNonNull(bugreportFd); Objects.requireNonNull(listener); @@ -429,9 +500,51 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { ensureUserCanTakeBugReport(bugreportMode); Slogf.i(TAG, "Starting bugreport for %s / %d", callingPackage, callingUid); - synchronized (mLock) { - startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd, - bugreportMode, bugreportFlags, listener, isScreenshotRequested); + final MutableBoolean handoffLock = new MutableBoolean(false); + if (sFeatureFlags.asyncStartBugreport()) { + synchronized (handoffLock) { + new Thread(()-> { + try { + synchronized (mLock) { + synchronized (handoffLock) { + handoffLock.value = true; + handoffLock.notifyAll(); + } + startBugreportLocked( + callingUid, + callingPackage, + bugreportFd, + screenshotFd, + bugreportMode, + bugreportFlags, + listener, + isScreenshotRequested); + } + } catch (Exception e) { + Slog.e(TAG, "Cannot start a new bugreport due to an unknown error", e); + reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR); + } + }, "BugreportManagerServiceThread").start(); + try { + while (!handoffLock.value) { // handle the rare case of a spurious wakeup + handoffLock.wait(DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS); + } + } catch (InterruptedException e) { + Slog.e(TAG, "Unexpectedly interrupted waiting for startBugreportLocked", e); + } + } + } else { + synchronized (mLock) { + startBugreportLocked( + callingUid, + callingPackage, + bugreportFd, + screenshotFd, + bugreportMode, + bugreportFlags, + listener, + isScreenshotRequested); + } } } @@ -465,7 +578,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { @RequiresPermission(value = Manifest.permission.DUMP, conditional = true) public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId, FileDescriptor bugreportFd, String bugreportFile, - boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) { + boolean keepBugreportOnRetrievalUnused, boolean skipUserConsentUnused, + IDumpstateListener listener) { int callingUid = Binder.getCallingUid(); enforcePermission(callingPackage, callingUid, false); @@ -496,9 +610,13 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { return; } + boolean skipUserConsent = mBugreportFileManager.canSkipConsentScreen( + callingPackage, /* isFullReport = */ false); + // Wrap the listener so we can intercept binder events directly. DumpstateListener myListener = new DumpstateListener(listener, ds, - new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true); + new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true, + !skipUserConsent, /* isDeferredReport = */ true); boolean keepBugreportOnRetrieval = false; if (onboardingBugreportV2Enabled()) { @@ -509,7 +627,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { setCurrentDumpstateListenerLocked(myListener); try { ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd, - bugreportFile, keepBugreportOnRetrieval, myListener); + bugreportFile, keepBugreportOnRetrieval, skipUserConsent, myListener); } catch (RemoteException e) { Slog.e(TAG, "RemoteException in retrieveBugreport", e); } @@ -710,7 +828,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } } - boolean reportFinishedFile = + boolean isDeferredConsentReport = (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0; boolean keepBugreportOnRetrieval = @@ -722,14 +840,17 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR); return; } - + boolean skipUserConsent = mBugreportFileManager.canSkipConsentScreen( + callingPackage, !isDeferredConsentReport); DumpstateListener myListener = new DumpstateListener(listener, ds, - new Pair<>(callingUid, callingPackage), reportFinishedFile, - keepBugreportOnRetrieval); + new Pair<>(callingUid, callingPackage), + /* reportFinishedFile = */ isDeferredConsentReport, keepBugreportOnRetrieval, + !isDeferredConsentReport && !skipUserConsent, + isDeferredConsentReport); setCurrentDumpstateListenerLocked(myListener); try { ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode, - bugreportFlags, myListener, isScreenshotRequested); + bugreportFlags, myListener, isScreenshotRequested, skipUserConsent); } catch (RemoteException e) { // dumpstate service is already started now. We need to kill it to manage the // lifecycle correctly. If we don't subsequent callers will get @@ -886,14 +1007,21 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { private boolean mDone; private boolean mKeepBugreportOnRetrieval; + private boolean mConsentGranted; + + private boolean mIsDeferredReport; + DumpstateListener(IDumpstateListener listener, IDumpstate ds, - Pair<Integer, String> caller, boolean reportFinishedFile) { - this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false); + Pair<Integer, String> caller, boolean reportFinishedFile, + boolean consentGranted, boolean isDeferredReport) { + this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false, + consentGranted, isDeferredReport); } DumpstateListener(IDumpstateListener listener, IDumpstate ds, Pair<Integer, String> caller, boolean reportFinishedFile, - boolean keepBugreportOnRetrieval) { + boolean keepBugreportOnRetrieval, boolean consentGranted, + boolean isDeferredReport) { if (DEBUG) { Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller); } @@ -902,6 +1030,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { mCaller = caller; mReportFinishedFile = reportFinishedFile; mKeepBugreportOnRetrieval = keepBugreportOnRetrieval; + mConsentGranted = consentGranted; + mIsDeferredReport = isDeferredReport; try { mDs.asBinder().linkToDeath(this, 0); } catch (RemoteException e) { @@ -941,6 +1071,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub { } else if (DEBUG) { Slog.d(TAG, "Not reporting finished file"); } + mBugreportFileManager.logConsentGrantedForCaller( + mCaller.second, mConsentGranted, mIsDeferredReport); mListener.onFinished(bugreportFile); } diff --git a/services/core/java/com/android/server/os/core_os_flags.aconfig b/services/core/java/com/android/server/os/core_os_flags.aconfig index ae33df83e3aa..efdc9b8c164f 100644 --- a/services/core/java/com/android/server/os/core_os_flags.aconfig +++ b/services/core/java/com/android/server/os/core_os_flags.aconfig @@ -7,3 +7,13 @@ flag { description: "Use proto tombstones as source of truth for adding to dropbox" bug: "323857385" } + +flag { + name: "async_start_bugreport" + namespace: "crumpet" + description: "Don't block callers on the start of dumpsys service" + bug: "180123623" + metadata { + purpose: PURPOSE_BUGFIX + } +} 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/DefaultCrossProfileIntentFiltersUtils.java b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java index 3862b79eb780..5ac883c58f03 100644 --- a/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java +++ b/services/core/java/com/android/server/pm/DefaultCrossProfileIntentFiltersUtils.java @@ -688,6 +688,29 @@ public class DefaultCrossProfileIntentFiltersUtils { ); } + /** Call intent should be handled by the main user. */ + private static final DefaultCrossProfileIntentFilter CALL_PRIVATE_PROFILE = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + SKIP_CURRENT_PROFILE, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_CALL) + .addCategory(Intent.CATEGORY_DEFAULT) + .addDataScheme("tel") + .addDataScheme("sip") + .addDataScheme("voicemail") + .build(); + + /** Pressing the call button should be handled by the main user. */ + private static final DefaultCrossProfileIntentFilter CALL_BUTTON_PRIVATE_PROFILE = + new DefaultCrossProfileIntentFilter.Builder( + DefaultCrossProfileIntentFilter.Direction.TO_PARENT, + ONLY_IF_NO_MATCH_FOUND, + /* letsPersonalDataIntoProfile= */ false) + .addAction(Intent.ACTION_CALL_BUTTON) + .addCategory(Intent.CATEGORY_DEFAULT) + .build(); + /** Dial intent with mime type can be handled by either private profile or its parent user. */ private static final DefaultCrossProfileIntentFilter DIAL_MIME_PRIVATE_PROFILE = new DefaultCrossProfileIntentFilter.Builder( @@ -755,6 +778,10 @@ public class DefaultCrossProfileIntentFiltersUtils { DIAL_MIME_PRIVATE_PROFILE, DIAL_DATA_PRIVATE_PROFILE, DIAL_RAW_PRIVATE_PROFILE, + CALL_PRIVATE_PROFILE, + CALL_BUTTON_PRIVATE_PROFILE, + EMERGENCY_CALL_DATA, + EMERGENCY_CALL_MIME, SMS_MMS_PRIVATE_PROFILE ); } diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index c60f0afcc2ff..209cbb7f591e 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -46,6 +46,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApexStagedEvent; +import android.content.pm.Flags; import android.content.pm.IPackageManagerNative; import android.content.pm.IStagedApexObserver; import android.content.pm.PackageManager; @@ -766,6 +767,10 @@ public final class DexOptHelper { final PackageSetting ps = installRequest.getScannedPackageSetting(); final AndroidPackage pkg = ps.getPkg(); final boolean onIncremental = isIncrementalPath(ps.getPathString()); + final boolean performDexOptForRollback = Flags.recoverabilityDetection() + ? !(installRequest.isRollback() + && installRequest.getInstallSource().mInitiatingPackageName.equals("android")) + : true; return (!instantApp || Global.getInt(context.getContentResolver(), Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) @@ -773,7 +778,8 @@ public final class DexOptHelper { && !pkg.isDebuggable() && (!onIncremental) && dexoptOptions.isCompilationEnabled() - && !isApex; + && !isApex + && performDexOptForRollback; } private static class StagedApexObserver extends IStagedApexObserver.Stub { diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 326d414d3d80..6cfa09f9adf1 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -37,7 +37,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_UID_CHANGED; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_STAGED; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; -import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH; +import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL; import static android.content.pm.PackageManager.UNINSTALL_REASON_UNKNOWN; import static android.content.pm.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V4; import static android.content.pm.parsing.ApkLiteParseUtils.isApkFile; @@ -505,27 +505,27 @@ final class InstallPackageHelper { // metadata file path for the new package. if (oldPkgSetting != null) { pkgSetting.setAppMetadataFilePath(null); + pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN); } // If the app metadata file path is not null then this is a system app with a preloaded app // metadata file on the system image. Do not reset the path and source if this is the // case. if (pkgSetting.getAppMetadataFilePath() == null) { - File dir = new File(pkg.getPath()); + String dir = pkg.getPath(); if (pkgSetting.isSystem()) { - dir = new File(Environment.getDataDirectory(), - "app-metadata/" + pkg.getPackageName()); + dir = Environment.getDataDirectoryPath() + "/app-metadata/" + pkg.getPackageName(); } - File appMetadataFile = new File(dir, APP_METADATA_FILE_NAME); - if (appMetadataFile.exists()) { - pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath()); + String appMetadataFilePath = dir + "/" + APP_METADATA_FILE_NAME; + if (request.hasAppMetadataFile()) { + pkgSetting.setAppMetadataFilePath(appMetadataFilePath); if (Flags.aslInApkAppMetadataSource()) { pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_INSTALLER); } } else if (Flags.aslInApkAppMetadataSource()) { Map<String, PackageManager.Property> properties = pkg.getProperties(); - if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) { + if (properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) { // ASL file extraction is done in post-install - pkgSetting.setAppMetadataFilePath(appMetadataFile.getAbsolutePath()); + pkgSetting.setAppMetadataFilePath(appMetadataFilePath); pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_APK); } } @@ -985,13 +985,13 @@ final class InstallPackageHelper { } void installPackagesTraced(List<InstallRequest> requests) { - synchronized (mPm.mInstallLock) { - try { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); - installPackagesLI(requests); - } finally { - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); - } + mPm.mInstallLock.lock(); + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installPackages"); + installPackagesLI(requests); + } finally { + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + mPm.mInstallLock.unlock(); } } @@ -2590,22 +2590,30 @@ final class InstallPackageHelper { final boolean performDexopt = DexOptHelper.shouldPerformDexopt(installRequest, dexoptOptions, mContext); if (performDexopt) { - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); + // dexopt can take long, and ArtService doesn't require installd, so we release + // the lock here and re-acquire the lock after dexopt is finished. + mPm.mInstallLock.unlock(); + try { + Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); - // This mirrors logic from commitReconciledScanResultLocked, where the library files - // needed for dexopt are assigned. - PackageSetting realPkgSetting = installRequest.getRealPackageSetting(); + // This mirrors logic from commitReconciledScanResultLocked, where the library + // files needed for dexopt are assigned. + PackageSetting realPkgSetting = installRequest.getRealPackageSetting(); - // Unfortunately, the updated system app flag is only tracked on this PackageSetting - boolean isUpdatedSystemApp = - installRequest.getScannedPackageSetting().isUpdatedSystemApp(); + // Unfortunately, the updated system app flag is only tracked on this + // PackageSetting + boolean isUpdatedSystemApp = + installRequest.getScannedPackageSetting().isUpdatedSystemApp(); - realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp); + realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp); - DexoptResult dexOptResult = - DexOptHelper.dexoptPackageUsingArtService(installRequest, dexoptOptions); - installRequest.onDexoptFinished(dexOptResult); - Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService( + installRequest, dexoptOptions); + installRequest.onDexoptFinished(dexOptResult); + Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); + } finally { + mPm.mInstallLock.lock(); + } } } PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental( @@ -2950,7 +2958,6 @@ final class InstallPackageHelper { info.mRemovedUsers = firstUserIds; info.mBroadcastUsers = firstUserIds; info.mUid = request.getAppId(); - info.mIsAppIdRemoved = true; info.mRemovedPackageVersionCode = request.getPkg().getLongVersionCode(); info.mRemovedForAllUsers = true; diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 6d385174a8a1..8f51e3696108 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -167,6 +167,8 @@ final class InstallRequest { private int mInstallerUidForInstallExisting = INVALID_UID; + private final boolean mHasAppMetadataFileFromInstaller; + // New install InstallRequest(InstallingSession params) { mUserId = params.getUser().getIdentifier(); @@ -185,6 +187,7 @@ final class InstallRequest { mSessionId = params.mSessionId; mRequireUserAction = params.mRequireUserAction; mPreVerifiedDomains = params.mPreVerifiedDomains; + mHasAppMetadataFileFromInstaller = params.mHasAppMetadataFile; } // Install existing package as user @@ -203,6 +206,7 @@ final class InstallRequest { mAppId = appId; mInstallerUidForInstallExisting = installerUid; mSystem = isSystem; + mHasAppMetadataFileFromInstaller = false; } // addForInit @@ -224,6 +228,7 @@ final class InstallRequest { mSessionId = -1; mRequireUserAction = USER_ACTION_UNSPECIFIED; mDisabledPs = disabledPs; + mHasAppMetadataFileFromInstaller = false; } @Nullable @@ -371,6 +376,10 @@ final class InstallRequest { return PackageInstallerSession.isArchivedInstallation(getInstallFlags()); } + public boolean hasAppMetadataFile() { + return mHasAppMetadataFileFromInstaller; + } + @Nullable public String getRemovedPackage() { return mRemovedInfo != null ? mRemovedInfo.mRemovedPackage : null; diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java index 4cbd3ad45dc7..b06c7cb4ac33 100644 --- a/services/core/java/com/android/server/pm/InstallingSession.java +++ b/services/core/java/com/android/server/pm/InstallingSession.java @@ -101,6 +101,7 @@ class InstallingSession { final boolean mApplicationEnabledSettingPersistent; @Nullable final DomainSet mPreVerifiedDomains; + final boolean mHasAppMetadataFile; // For move install InstallingSession(OriginInfo originInfo, MoveInfo moveInfo, IPackageInstallObserver2 observer, @@ -134,12 +135,14 @@ class InstallingSession { mRequireUserAction = USER_ACTION_UNSPECIFIED; mApplicationEnabledSettingPersistent = false; mPreVerifiedDomains = null; + mHasAppMetadataFile = false; } InstallingSession(int sessionId, File stagedDir, IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, InstallSource installSource, UserHandle user, SigningDetails signingDetails, int installerUid, - PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm) { + PackageLite packageLite, DomainSet preVerifiedDomains, PackageManagerService pm, + boolean hasAppMetadatafile) { mPm = pm; mUser = user; mOriginInfo = OriginInfo.fromStagedFile(stagedDir); @@ -168,6 +171,7 @@ class InstallingSession { mRequireUserAction = sessionParams.requireUserAction; mApplicationEnabledSettingPersistent = sessionParams.applicationEnabledSettingPersistent; mPreVerifiedDomains = preVerifiedDomains; + mHasAppMetadataFile = hasAppMetadatafile; } @Override diff --git a/services/core/java/com/android/server/pm/PackageFreezer.java b/services/core/java/com/android/server/pm/PackageFreezer.java index 7c5615771607..0afda4598bcb 100644 --- a/services/core/java/com/android/server/pm/PackageFreezer.java +++ b/services/core/java/com/android/server/pm/PackageFreezer.java @@ -18,6 +18,7 @@ package com.android.server.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.pm.Flags; import android.content.pm.PackageManager; import dalvik.system.CloseGuard; @@ -76,8 +77,13 @@ final class PackageFreezer implements AutoCloseable { ps = mPm.mSettings.getPackageLPr(mPackageName); } if (ps != null) { - mPm.killApplication(ps.getPackageName(), ps.getAppId(), userId, killReason, - exitInfoReason); + if (Flags.waitApplicationKilled()) { + mPm.killApplicationSync(ps.getPackageName(), ps.getAppId(), userId, killReason, + exitInfoReason); + } else { + mPm.killApplication(ps.getPackageName(), ps.getAppId(), userId, killReason, + exitInfoReason); + } } mCloseGuard.open("close"); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 80a5f3a4c579..57f6d2789dc5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -601,6 +601,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @GuardedBy("mLock") private String mSessionErrorMessage; + @GuardedBy("mLock") + private boolean mHasAppMetadataFile = false; + @Nullable final StagedSession mStagedSession; @@ -1814,7 +1817,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertCallerIsOwnerOrRoot(); synchronized (mLock) { assertPreparedAndNotCommittedOrDestroyedLocked("getAppMetadataFd"); - if (!getStagedAppMetadataFile().exists()) { + if (!mHasAppMetadataFile) { return null; } try { @@ -1827,9 +1830,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Override public void removeAppMetadata() { - File file = getStagedAppMetadataFile(); - if (file.exists()) { - file.delete(); + synchronized (mLock) { + if (mHasAppMetadataFile) { + getStagedAppMetadataFile().delete(); + mHasAppMetadataFile = false; + } } } @@ -1850,8 +1855,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { assertPreparedAndNotSealedLocked("openWriteAppMetadata"); } try { - return doWriteInternal(APP_METADATA_FILE_NAME, /* offsetBytes= */ 0, + ParcelFileDescriptor fd = doWriteInternal(APP_METADATA_FILE_NAME, /* offsetBytes= */ 0, /* lengthBytes= */ -1, null); + synchronized (mLock) { + mHasAppMetadataFile = true; + } + return fd; } catch (IOException e) { throw ExceptionUtils.wrap(e); } @@ -2145,18 +2154,21 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - File appMetadataFile = getStagedAppMetadataFile(); - if (appMetadataFile.exists()) { - long sizeLimit = getAppMetadataSizeLimit(); - if (appMetadataFile.length() > sizeLimit) { - appMetadataFile.delete(); - throw new IllegalArgumentException( - "App metadata size exceeds the maximum allowed limit of " + sizeLimit); - } - if (isIncrementalInstallation()) { - // Incremental requires stageDir to be empty so move the app metadata file to a - // temporary location and move back after commit. - appMetadataFile.renameTo(getTmpAppMetadataFile()); + synchronized (mLock) { + if (mHasAppMetadataFile) { + File appMetadataFile = getStagedAppMetadataFile(); + long sizeLimit = getAppMetadataSizeLimit(); + if (appMetadataFile.length() > sizeLimit) { + appMetadataFile.delete(); + mHasAppMetadataFile = false; + throw new IllegalArgumentException( + "App metadata size exceeds the maximum allowed limit of " + sizeLimit); + } + if (isIncrementalInstallation()) { + // Incremental requires stageDir to be empty so move the app metadata file to a + // temporary location and move back after commit. + appMetadataFile.renameTo(getTmpAppMetadataFile()); + } } } @@ -3207,7 +3219,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { synchronized (mLock) { return new InstallingSession(sessionId, stageDir, localObserver, params, mInstallSource, - user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm); + user, mSigningDetails, mInstallerUid, mPackageLite, mPreVerifiedDomains, mPm, + mHasAppMetadataFile); } } @@ -3445,9 +3458,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + if (mHasAppMetadataFile && !getStagedAppMetadataFile().exists()) { + throw new PackageManagerException(INSTALL_FAILED_VERIFICATION_FAILURE, + "App metadata file expected but not found in " + stageDir.getAbsolutePath()); + } + final List<ApkLite> addedFiles = getAddedApkLitesLocked(); if (addedFiles.isEmpty() - && (removeSplitList.size() == 0 || getStagedAppMetadataFile().exists())) { + && (removeSplitList.size() == 0 || mHasAppMetadataFile)) { throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, TextUtils.formatSimple("Session: %d. No packages staged in %s", sessionId, stageDir.getAbsolutePath())); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ae485ede1bec..f8fceda0582c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -53,6 +53,7 @@ import android.annotation.StringRes; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.ActivityManager; +import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.ApplicationExitInfo; import android.app.ApplicationPackageManager; @@ -626,7 +627,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService // Lock for state used when installing and doing other long running // operations. Methods that must be called with this lock held have // the suffix "LI". - final Object mInstallLock; + final PackageManagerTracedLock mInstallLock; // ---------------------------------------------------------------- @@ -1692,8 +1693,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing", Trace.TRACE_TAG_PACKAGE_MANAGER); t.traceBegin("create package manager"); - final PackageManagerTracedLock lock = new PackageManagerTracedLock(); - final Object installLock = new Object(); + final PackageManagerTracedLock lock = new PackageManagerTracedLock("mLock"); + final PackageManagerTracedLock installLock = new PackageManagerTracedLock("mInstallLock"); HandlerThread backgroundThread = new ServiceThread("PackageManagerBg", Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); @@ -3132,6 +3133,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + void killApplicationSync(String pkgName, @AppIdInt int appId, + @UserIdInt int userId, String reason, int exitInfoReason) { + ActivityManagerInternal mAmi = LocalServices.getService(ActivityManagerInternal.class); + if (mAmi != null) { + if (Thread.holdsLock(mLock)) { + // holds PM's lock, go back killApplication to avoid it run into watchdog reset. + Slog.e(TAG, "Holds PM's locker, unable kill application synchronized"); + killApplication(pkgName, appId, userId, reason, exitInfoReason); + } else { + mAmi.killApplicationSync(pkgName, appId, userId, reason, exitInfoReason); + } + } + } + @Override public void notifyPackageAdded(String packageName, int uid) { mPackageObserverHelper.notifyAdded(packageName, uid); @@ -4003,7 +4018,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService final PackageMetrics.ComponentStateMetrics componentStateMetrics = new PackageMetrics.ComponentStateMetrics(setting, UserHandle.getUid(userId, packageSetting.getAppId()), - packageSetting.getEnabled(userId)); + packageSetting.getEnabled(userId), callingUid); if (!setEnabledSettingInternalLocked(computer, packageSetting, setting, userId, callingPackage)) { continue; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java index 83f3b16b31d1..ae2eaeb31f81 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java @@ -86,7 +86,7 @@ public class PackageManagerServiceInjector { private final Context mContext; private final PackageManagerTracedLock mLock; private final Installer mInstaller; - private final Object mInstallLock; + private final PackageManagerTracedLock mInstallLock; private final Handler mBackgroundHandler; private final Executor mBackgroundExecutor; private final List<ScanPartition> mSystemPartitions; @@ -144,7 +144,7 @@ public class PackageManagerServiceInjector { private final Singleton<PackageMonitorCallbackHelper> mPackageMonitorCallbackHelper; PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock, - Installer installer, Object installLock, PackageAbiHelper abiHelper, + Installer installer, PackageManagerTracedLock installLock, PackageAbiHelper abiHelper, Handler backgroundHandler, List<ScanPartition> systemPartitions, Producer<ComponentResolver> componentResolverProducer, @@ -254,7 +254,7 @@ public class PackageManagerServiceInjector { return mAbiHelper; } - public Object getInstallLock() { + public PackageManagerTracedLock getInstallLock() { return mInstallLock; } diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index fa54f6e69b19..6700f00a8856 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -19,7 +19,7 @@ package com.android.server.pm; import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE; -import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL_PATH; +import static android.content.pm.PackageManager.PROPERTY_ANDROID_SAFETY_LABEL; import static android.content.pm.SigningDetails.CertCapabilities.SHARED_USER_ID; import static android.system.OsConstants.O_CREAT; import static android.system.OsConstants.O_RDWR; @@ -71,8 +71,12 @@ import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; +import android.content.res.ApkAssets; +import android.content.res.AssetManager; +import android.content.res.Resources; import android.os.Binder; import android.os.Build; +import android.os.CancellationSignal; import android.os.Debug; import android.os.Environment; import android.os.FileUtils; @@ -93,6 +97,7 @@ import android.system.Os; import android.util.ArraySet; import android.util.AtomicFile; import android.util.Base64; +import android.util.DisplayMetrics; import android.util.Log; import android.util.LogPrinter; import android.util.Printer; @@ -147,11 +152,10 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Predicate; import java.util.zip.GZIPInputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; /** * Class containing helper methods for the PackageManagerService. @@ -1248,7 +1252,7 @@ public class PackageManagerServiceUtils { } final ParsedMainComponent comp = componentInfoToComponent( resolveInfo.getComponentInfo(), resolver, isReceiver); - if (!comp.getIntents().isEmpty() && intent.getAction() == null) { + if (comp != null && !comp.getIntents().isEmpty() && intent.getAction() == null) { match = false; } } else if (c instanceof IntentFilter) { @@ -1667,45 +1671,63 @@ public class PackageManagerServiceUtils { if (appMetadataFile.exists()) { return true; } - if (isSystem) { + Map<String, Property> properties = pkg.getProperties(); + if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL)) { + return false; + } + Property fileInApkProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL); + if (!fileInApkProperty.isResourceId()) { + return false; + } + if (isSystem && !appMetadataFile.getParentFile().exists()) { try { - makeDirRecursive(new File(appMetadataFilePath).getParentFile(), 0700); + makeDirRecursive(appMetadataFile.getParentFile(), 0700); } catch (Exception e) { Slog.e(TAG, "Failed to create app metadata dir for package " + pkg.getPackageName() + ": " + e.getMessage()); return false; } } - Map<String, Property> properties = pkg.getProperties(); - if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) { - return false; - } - Property fileInAPkPathProperty = properties.get(PROPERTY_ANDROID_SAFETY_LABEL_PATH); - if (!fileInAPkPathProperty.isString()) { - return false; - } - String fileInApkPath = fileInAPkPathProperty.getString(); List<AndroidPackageSplit> splits = pkg.getSplits(); + AssetManager.Builder builder = new AssetManager.Builder(); for (int i = 0; i < splits.size(); i++) { - try (ZipFile zipFile = new ZipFile(splits.get(i).getPath())) { - ZipEntry zipEntry = zipFile.getEntry(fileInApkPath); - if (zipEntry != null - && (isSystem || zipEntry.getSize() <= getAppMetadataSizeLimit())) { - try (InputStream in = zipFile.getInputStream(zipEntry)) { - try (FileOutputStream out = new FileOutputStream(appMetadataFile)) { - FileUtils.copy(in, out); - Os.chmod(appMetadataFile.getAbsolutePath(), - APP_METADATA_FILE_ACCESS_MODE); - return true; + try { + builder.addApkAssets(ApkAssets.loadFromPath(splits.get(i).getPath())); + } catch (IOException e) { + Slog.e(TAG, "Failed to load resources from APK " + splits.get(i).getPath()); + } + } + AssetManager assetManager = builder.build(); + DisplayMetrics displayMetrics = new DisplayMetrics(); + displayMetrics.setToDefaults(); + Resources res = new Resources(assetManager, displayMetrics, null); + AtomicBoolean copyFailed = new AtomicBoolean(false); + try (InputStream in = res.openRawResource(fileInApkProperty.getResourceId())) { + try (FileOutputStream out = new FileOutputStream(appMetadataFile)) { + if (isSystem) { + FileUtils.copy(in, out); + } else { + long sizeLimit = getAppMetadataSizeLimit(); + CancellationSignal signal = new CancellationSignal(); + FileUtils.copy(in, out, signal, Runnable::run, (long progress) -> { + if (progress > sizeLimit) { + copyFailed.set(true); + signal.cancel(); } - } + }); } - } catch (Exception e) { - Slog.e(TAG, e.getMessage()); + Os.chmod(appMetadataFile.getAbsolutePath(), + APP_METADATA_FILE_ACCESS_MODE); + } + } catch (Exception e) { + Slog.e(TAG, e.getMessage()); + copyFailed.set(true); + } finally { + if (copyFailed.get()) { appMetadataFile.delete(); } } - return false; + return !copyFailed.get(); } public static void linkFilesToOldDirs(@NonNull Installer installer, diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 179379487aba..0a8b2b2c6219 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -35,6 +35,8 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.role.RoleManager; +import android.app.usage.StorageStats; +import android.app.usage.StorageStatsManager; import android.content.ComponentName; import android.content.Context; import android.content.IIntentReceiver; @@ -136,6 +138,7 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.URISyntaxException; import java.security.SecureRandom; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; @@ -275,6 +278,8 @@ class PackageManagerShellCommand extends ShellCommand { return runClear(); case "get-archived-package-metadata": return runGetArchivedPackageMetadata(); + case "get-package-storage-stats": + return runGetPackageStorageStats(); case "install-archived": return runArchivedInstall(); case "enable": @@ -323,6 +328,8 @@ class PackageManagerShellCommand extends ShellCommand { return runGetPrivappDenyPermissions(); case "get-oem-permissions": return runGetOemPermissions(); + case "get-signature-permission-allowlist": + return runGetSignaturePermissionAllowlist(); case "trim-caches": return runTrimCaches(); case "create-user": @@ -1861,6 +1868,103 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + /** + * Returns a string that shows the number of bytes in b, Kb, Mb or Gb. + */ + protected static String getFormattedBytes(long size) { + double k = size/1024.0; + double m = size/1048576.0; + double g = size/1073741824.0; + + DecimalFormat dec = new DecimalFormat("0.00"); + if (g > 1) { + return dec.format(g).concat(" Gb"); + } else if (m > 1) { + return dec.format(m).concat(" Mb"); + } else if (k > 1) { + return dec.format(k).concat(" Kb"); + } + return ""; + } + + /** + * Return the string that displays the data size. + */ + private String getDataSizeDisplay(long size) { + String formattedOutput = getFormattedBytes(size); + if (!formattedOutput.isEmpty()) { + formattedOutput = " (" + formattedOutput + ")"; + } + return Long.toString(size) + " bytes" + formattedOutput; + } + + /** + * Display storage stats of the specified package. + * + * Usage: get-package-storage-stats [--usr USER_ID] PACKAGE + */ + private int runGetPackageStorageStats() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + if (!android.content.pm.Flags.getPackageStorageStats()) { + pw.println("Error: get_package_storage_stats flag is not enabled"); + return 1; + } + if (!android.app.usage.Flags.getAppBytesByDataTypeApi()) { + pw.println("Error: get_app_bytes_by_data_type_api flag is not enabled"); + return 1; + } + int userId = UserHandle.USER_CURRENT; + + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--user": + userId = UserHandle.parseUserArg(getNextArgRequired()); + break; + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + + final String packageName = getNextArg(); + if (packageName == null) { + pw.println("Error: package name not specified"); + return 1; + } + try { + StorageStatsManager storageStatsManager = + mContext.getSystemService(StorageStatsManager.class); + final int translatedUserId = translateUserId(userId, UserHandle.USER_NULL, + "runGetPackageStorageStats"); + StorageStats stats = + storageStatsManager.queryStatsForPackage(StorageManager.UUID_DEFAULT, + packageName, UserHandle.of(translatedUserId)); + + pw.println("code: " + getDataSizeDisplay(stats.getAppBytes())); + pw.println("data: " + getDataSizeDisplay(stats.getDataBytes())); + pw.println("cache: " + getDataSizeDisplay(stats.getCacheBytes())); + pw.println("apk: " + getDataSizeDisplay(stats.getAppBytesByDataType( + StorageStats.APP_DATA_TYPE_FILE_TYPE_APK))); + pw.println("lib: " + getDataSizeDisplay( + stats.getAppBytesByDataType(StorageStats.APP_DATA_TYPE_LIB))); + pw.println("dm: " + getDataSizeDisplay(stats.getAppBytesByDataType( + StorageStats.APP_DATA_TYPE_FILE_TYPE_DM))); + pw.println("dexopt artifacts: " + getDataSizeDisplay(stats.getAppBytesByDataType( + StorageStats.APP_DATA_TYPE_FILE_TYPE_DEXOPT_ARTIFACT))); + pw.println("current profile : " + getDataSizeDisplay(stats.getAppBytesByDataType( + StorageStats.APP_DATA_TYPE_FILE_TYPE_CURRENT_PROFILE))); + pw.println("reference profile: " + getDataSizeDisplay(stats.getAppBytesByDataType( + StorageStats.APP_DATA_TYPE_FILE_TYPE_REFERENCE_PROFILE))); + pw.println("external cache: " + getDataSizeDisplay(stats.getExternalCacheBytes())); + } catch (Exception e) { + getErrPrintWriter().println("Failed to get storage stats, reason: " + e); + pw.println("Failure [failed to get storage stats], reason: " + e); + return -1; + } + return 0; + } + private int runInstallExisting() throws RemoteException { final PrintWriter pw = getOutPrintWriter(); int userId = UserHandle.USER_CURRENT; @@ -2818,6 +2922,54 @@ class PackageManagerShellCommand extends ShellCommand { return 0; } + private int runGetSignaturePermissionAllowlist() { + final var partition = getNextArg(); + if (partition == null) { + getErrPrintWriter().println("Error: no partition specified."); + return 1; + } + final var permissionAllowlist = + SystemConfig.getInstance().getPermissionAllowlist(); + final ArrayMap<String, ArrayMap<String, Boolean>> allowlist; + switch (partition) { + case "system": + allowlist = permissionAllowlist.getSignatureAppAllowlist(); + break; + case "vendor": + allowlist = permissionAllowlist.getVendorSignatureAppAllowlist(); + break; + case "product": + allowlist = permissionAllowlist.getProductSignatureAppAllowlist(); + break; + case "system-ext": + allowlist = permissionAllowlist.getSystemExtSignatureAppAllowlist(); + break; + default: + getErrPrintWriter().println("Error: unknown partition: " + partition); + return 1; + } + final var ipw = new IndentingPrintWriter(getOutPrintWriter(), " "); + final var allowlistSize = allowlist.size(); + for (var allowlistIndex = 0; allowlistIndex < allowlistSize; allowlistIndex++) { + final var packageName = allowlist.keyAt(allowlistIndex); + final var permissions = allowlist.valueAt(allowlistIndex); + ipw.print("Package: "); + ipw.println(packageName); + ipw.increaseIndent(); + final var permissionsSize = permissions.size(); + for (var permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) { + final var permissionName = permissions.keyAt(permissionsIndex); + final var granted = permissions.valueAt(permissionsIndex); + if (granted) { + ipw.print("Permission: "); + ipw.println(permissionName); + } + } + ipw.decreaseIndent(); + } + return 0; + } + private int runTrimCaches() throws RemoteException { String size = getNextArg(); if (size == null) { @@ -4750,6 +4902,10 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" get-oem-permissions TARGET-PACKAGE"); pw.println(" Prints all OEM permissions for a package."); pw.println(""); + pw.println(" get-signature-permission-allowlist PARTITION"); + pw.println(" Prints the signature permission allowlist for a partition."); + pw.println(" PARTITION is one of system, vendor, product and system-ext"); + pw.println(""); pw.println(" trim-caches DESIRED_FREE_SPACE [internal|UUID]"); pw.println(" Trim cache files to reach the given free space."); pw.println(""); @@ -4869,6 +5025,8 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" Displays the component name of the domain verification agent on device."); pw.println(" If the component isn't enabled, an error message will be displayed."); pw.println(" --user: return the agent of the given user (SYSTEM_USER if unspecified)"); + pw.println(" get-package-storage-stats [--user <USER_ID>] <PACKAGE>"); + pw.println(" Return the storage stats for the given app, if present"); pw.println(""); printArtServiceHelp(); pw.println(""); diff --git a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java index 75e1803f1695..303b8b9dd817 100644 --- a/services/core/java/com/android/server/pm/PackageManagerTracedLock.java +++ b/services/core/java/com/android/server/pm/PackageManagerTracedLock.java @@ -16,6 +16,9 @@ package com.android.server.pm; +import android.annotation.Nullable; +import android.util.Slog; + import java.util.concurrent.locks.ReentrantLock; /** @@ -23,4 +26,31 @@ import java.util.concurrent.locks.ReentrantLock; * injection, similar to {@link ActivityManagerGlobalLock}. */ public class PackageManagerTracedLock extends ReentrantLock { + private static final String TAG = "PackageManagerTracedLock"; + private static final boolean DEBUG = false; + @Nullable private final String mLockName; + + public PackageManagerTracedLock(@Nullable String lockName) { + mLockName = lockName; + } + + public PackageManagerTracedLock() { + this(null); + } + + @Override + public void lock() { + super.lock(); + if (DEBUG && mLockName != null) { + Slog.i(TAG, "locked " + mLockName); + } + } + + @Override + public void unlock() { + super.unlock(); + if (DEBUG && mLockName != null) { + Slog.i(TAG, "unlocked " + mLockName); + } + } } diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java index 20598f91a51d..2081f73e7336 100644 --- a/services/core/java/com/android/server/pm/PackageMetrics.java +++ b/services/core/java/com/android/server/pm/PackageMetrics.java @@ -358,6 +358,7 @@ final class PackageMetrics { public static class ComponentStateMetrics { public int mUid; + public int mCallingUid; public int mComponentOldState; public int mComponentNewState; public boolean mIsForWholeApp; @@ -365,13 +366,14 @@ final class PackageMetrics { @Nullable private String mClassName; ComponentStateMetrics(@NonNull PackageManager.ComponentEnabledSetting setting, int uid, - int componentOldState) { + int componentOldState, int callingUid) { mUid = uid; mComponentOldState = componentOldState; mComponentNewState = setting.getEnabledState(); mIsForWholeApp = !setting.isComponent(); mPackageName = setting.getPackageName(); mClassName = setting.getClassName(); + mCallingUid = callingUid; } public boolean isSameComponent(ActivityInfo activityInfo) { @@ -412,14 +414,15 @@ final class PackageMetrics { componentStateMetrics.mComponentOldState, componentStateMetrics.mComponentNewState, isLauncher, - componentStateMetrics.mIsForWholeApp); + componentStateMetrics.mIsForWholeApp, + componentStateMetrics.mCallingUid); } } private static void reportComponentStateChanged(int uid, int componentOldState, - int componentNewState, boolean isLauncher, boolean isForWholeApp) { + int componentNewState, boolean isLauncher, boolean isForWholeApp, int callingUid) { FrameworkStatsLog.write(FrameworkStatsLog.COMPONENT_STATE_CHANGED_REPORTED, - uid, componentOldState, componentNewState, isLauncher, isForWholeApp); + uid, componentOldState, componentNewState, isLauncher, isForWholeApp, callingUid); } private static List<ResolveInfo> getHomeActivitiesResolveInfoAsUser(@NonNull Computer computer, 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..8d6d774a9959 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -2139,10 +2139,18 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile continue; } + ComponentName unflattenOriginalComponentName = ComponentName.unflattenFromString( + originalComponentName); + if (unflattenOriginalComponentName == null) { + Slog.wtf(TAG, "Incorrect component name: " + originalComponentName + + " 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/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 82902d45f1f2..9edf3b14bad7 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -172,7 +172,7 @@ public class ShortcutService extends IShortcutService.Stub { static final boolean DEBUG = false; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true - static final boolean DEBUG_REBOOT = false; // STOPSHIP if true + static final boolean DEBUG_REBOOT = true; @VisibleForTesting static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day @@ -3798,24 +3798,36 @@ public class ShortcutService extends IShortcutService.Stub { final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); final boolean archival = intent.getBooleanExtra(Intent.EXTRA_ARCHIVAL, false); + Slog.d(TAG, "received package broadcast intent: " + intent); switch (action) { case Intent.ACTION_PACKAGE_ADDED: if (replacing) { + Slog.d(TAG, "replacing package: " + packageName + " userId" + userId); handlePackageUpdateFinished(packageName, userId); } else { + Slog.d(TAG, "adding package: " + packageName + " userId" + userId); handlePackageAdded(packageName, userId); } break; case Intent.ACTION_PACKAGE_REMOVED: if (!replacing || (replacing && archival)) { + if (!replacing) { + Slog.d(TAG, "removing package: " + + packageName + " userId" + userId); + } else if (archival) { + Slog.d(TAG, "archiving package: " + + packageName + " userId" + userId); + } handlePackageRemoved(packageName, userId); } break; case Intent.ACTION_PACKAGE_CHANGED: + Slog.d(TAG, "changing package: " + packageName + " userId" + userId); handlePackageChanged(packageName, userId); - break; case Intent.ACTION_PACKAGE_DATA_CLEARED: + Slog.d(TAG, "clearing data for package: " + + packageName + " userId" + userId); handlePackageDataCleared(packageName, userId); break; } diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java index 1d414011cff3..ef32485567f8 100644 --- a/services/core/java/com/android/server/pm/UserDataPreparer.java +++ b/services/core/java/com/android/server/pm/UserDataPreparer.java @@ -52,11 +52,11 @@ class UserDataPreparer { private static final String TAG = "UserDataPreparer"; private static final String XATTR_SERIAL = "user.serial"; - private final Object mInstallLock; + private final PackageManagerTracedLock mInstallLock; private final Context mContext; private final Installer mInstaller; - UserDataPreparer(Installer installer, Object installLock, Context context) { + UserDataPreparer(Installer installer, PackageManagerTracedLock installLock, Context context) { mInstallLock = installLock; mContext = context; mInstaller = installer; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 3c702b4125b7..ebdca5bcec6f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -600,8 +600,11 @@ public class UserManagerService extends IUserManager.Stub { public void onReceive(Context context, Intent intent) { if (isAutoLockForPrivateSpaceEnabled()) { if (ACTION_SCREEN_OFF.equals(intent.getAction())) { + Slog.d(LOG_TAG, "SCREEN_OFF broadcast received"); maybeScheduleMessageToAutoLockPrivateSpace(); } else if (ACTION_SCREEN_ON.equals(intent.getAction())) { + Slog.d(LOG_TAG, "SCREEN_ON broadcast received, " + + "removing queued message to auto-lock private space"); // Remove any queued messages since the device is interactive again mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN); } @@ -619,6 +622,8 @@ public class UserManagerService extends IUserManager.Stub { getMainUserIdUnchecked()); if (privateSpaceAutoLockPreference != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) { + Slogf.d(LOG_TAG, "Not scheduling auto-lock on inactivity," + + "preference is set to %d", privateSpaceAutoLockPreference); return; } int privateProfileUserId = getPrivateProfileUserId(); @@ -632,6 +637,7 @@ public class UserManagerService extends IUserManager.Stub { @VisibleForTesting void scheduleMessageToAutoLockPrivateSpace(int userId, Object token, long delayInMillis) { + Slog.i(LOG_TAG, "Scheduling auto-lock message"); mHandler.postDelayed(() -> { final PowerManager powerManager = mContext.getSystemService(PowerManager.class); if (powerManager != null && !powerManager.isInteractive()) { @@ -1060,8 +1066,6 @@ public class UserManagerService extends IUserManager.Stub { if (isAutoLockingPrivateSpaceOnRestartsEnabled()) { autoLockPrivateSpace(); } - - markEphemeralUsersForRemoval(); } private boolean isAutoLockingPrivateSpaceOnRestartsEnabled() { @@ -1098,21 +1102,6 @@ public class UserManagerService extends IUserManager.Stub { } } - /** Marks all ephemeral users as slated for deletion. **/ - private void markEphemeralUsersForRemoval() { - synchronized (mUsersLock) { - final int userSize = mUsers.size(); - for (int i = 0; i < userSize; i++) { - final UserInfo ui = mUsers.valueAt(i).info; - if (ui.isEphemeral() && !ui.preCreated && ui.id != UserHandle.USER_SYSTEM) { - addRemovingUserIdLocked(ui.id); - ui.partial = true; - ui.flags |= UserInfo.FLAG_DISABLED; - } - } - } - } - /* Prunes out any partially created or partially removed users. */ private void cleanupPartialUsers() { ArrayList<UserInfo> partials = new ArrayList<>(); @@ -1935,16 +1924,20 @@ public class UserManagerService extends IUserManager.Stub { private void showConfirmCredentialToDisableQuietMode( @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) { if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) { - // TODO (b/308121702) It may be brittle to rely on user states to check profile state - int state; - synchronized (mUserStates) { - state = mUserStates.get(userId, UserState.STATE_NONE); - } - if (state != UserState.STATE_NONE) { - Slog.i(LOG_TAG, - "showConfirmCredentialToDisableQuietMode() called too early, user " + userId - + " is still alive."); - return; + if (!android.multiuser.Flags.restrictQuietModeCredentialBugFixToManagedProfiles() + || getUserInfo(userId).isManagedProfile()) { + // TODO (b/308121702) It may be brittle to rely on user states to check managed + // profile state + int state; + synchronized (mUserStates) { + state = mUserStates.get(userId, UserState.STATE_NONE); + } + if (state != UserState.STATE_NONE) { + Slog.i(LOG_TAG, + "showConfirmCredentialToDisableQuietMode() called too early, managed " + + "user " + userId + " is still alive."); + return; + } } } // otherwise, we show a profile challenge to trigger decryption of the user @@ -4223,6 +4216,13 @@ public class UserManagerService extends IUserManager.Stub { || mNextSerialNumber <= userData.info.id) { mNextSerialNumber = userData.info.id + 1; } + if (userData.info.isEphemeral() && !userData.info.preCreated + && userData.info.id != UserHandle.USER_SYSTEM) { + // Mark ephemeral user as slated for deletion. + addRemovingUserIdLocked(userData.info.id); + userData.info.partial = true; + userData.info.flags |= UserInfo.FLAG_DISABLED; + } } } } else if (name.equals(TAG_GUEST_RESTRICTIONS)) { @@ -7155,6 +7155,7 @@ public class UserManagerService extends IUserManager.Stub { synchronized (mUsersLock) { pw.println(" Boot user: " + mBootUser); } + pw.println("Can add private profile: "+ canAddPrivateProfile(currentUserId)); pw.println(); pw.println("Number of listeners for"); 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/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java index 1a9e012a7c53..f7eb29fe3ee9 100644 --- a/services/core/java/com/android/server/pm/VerifyingSession.java +++ b/services/core/java/com/android/server/pm/VerifyingSession.java @@ -141,6 +141,8 @@ final class VerifyingSession { @NonNull private final PackageManagerService mPm; + private final int mInstallReason; + VerifyingSession(UserHandle user, File stagedDir, IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams, InstallSource installSource, int installerUid, SigningDetails signingDetails, int sessionId, PackageLite lite, @@ -168,6 +170,7 @@ final class VerifyingSession { mUserActionRequiredType = sessionParams.requireUserAction; mIsInherit = sessionParams.mode == MODE_INHERIT_EXISTING; mIsStaged = sessionParams.isStaged; + mInstallReason = sessionParams.installReason; } @Override @@ -190,7 +193,9 @@ final class VerifyingSession { // Perform package verification and enable rollback (unless we are simply moving the // package). if (!mOriginInfo.mExisting) { - if (!isApex() && !isArchivedInstallation()) { + final boolean verifyForRollback = Flags.recoverabilityDetection() + ? !isARollback() : true; + if (!isApex() && !isArchivedInstallation() && verifyForRollback) { // TODO(b/182426975): treat APEX as APK when APK verification is concerned sendApkVerificationRequest(pkgLite); } @@ -200,6 +205,11 @@ final class VerifyingSession { } } + private boolean isARollback() { + return mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK + && mInstallSource.mInitiatingPackageName.equals("android"); + } + private void sendApkVerificationRequest(PackageInfoLite pkgLite) { final int verificationId = mPm.mPendingVerificationToken++; diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index 5fa8856f2940..11b9e776204c 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -64,6 +64,7 @@ import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.power.feature.PowerManagerFlags; import com.android.server.statusbar.StatusBarManagerInternal; import java.io.PrintWriter; @@ -187,11 +188,16 @@ public class Notifier { private final AtomicBoolean mIsPlayingChargingStartedFeedback = new AtomicBoolean(false); + private final Injector mInjector; + + private final PowerManagerFlags mFlags; + public Notifier(Looper looper, Context context, IBatteryStats batteryStats, SuspendBlocker suspendBlocker, WindowManagerPolicy policy, FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector, - Executor backgroundExecutor) { + Executor backgroundExecutor, PowerManagerFlags powerManagerFlags, Injector injector) { mContext = context; + mFlags = powerManagerFlags; mBatteryStats = batteryStats; mAppOps = mContext.getSystemService(AppOpsManager.class); mSuspendBlocker = suspendBlocker; @@ -224,8 +230,8 @@ public class Notifier { mShowWirelessChargingAnimationConfig = context.getResources().getBoolean( com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim); - mWakeLockLog = new WakeLockLog(context); - + mInjector = (injector == null) ? new RealInjector() : injector; + mWakeLockLog = mInjector.getWakeLockLog(context); // Initialize interactive state for battery stats. try { mBatteryStats.noteInteractive(true); @@ -267,7 +273,7 @@ public class Notifier { + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + ", workSource=" + workSource); } - notifyWakeLockListener(callback, tag, true); + notifyWakeLockListener(callback, tag, true, ownerUid, flags); final int monitorType = getBatteryStatsWakeLockMonitorType(flags); if (monitorType >= 0) { try { @@ -287,8 +293,9 @@ public class Notifier { } } - mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags); - + if (!mFlags.improveWakelockLatency()) { + mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, /*eventTime=*/ -1); + } mWakefulnessSessionObserver.onWakeLockAcquired(flags); } @@ -406,7 +413,7 @@ public class Notifier { + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + ", workSource=" + workSource); } - notifyWakeLockListener(callback, tag, false); + notifyWakeLockListener(callback, tag, false, ownerUid, flags); final int monitorType = getBatteryStatsWakeLockMonitorType(flags); if (monitorType >= 0) { try { @@ -422,8 +429,9 @@ public class Notifier { // Ignore } } - mWakeLockLog.onWakeLockReleased(tag, ownerUid); - + if (!mFlags.improveWakelockLatency()) { + mWakeLockLog.onWakeLockReleased(tag, ownerUid, /*eventTime=*/ -1); + } mWakefulnessSessionObserver.onWakeLockReleased(flags, releaseReason); } @@ -1040,10 +1048,19 @@ public class Notifier { return enabled && dndOff; } - private void notifyWakeLockListener(IWakeLockCallback callback, String tag, boolean isEnabled) { + private void notifyWakeLockListener(IWakeLockCallback callback, String tag, boolean isEnabled, + int ownerUid, int flags) { if (callback != null) { + long currentTime = mInjector.currentTimeMillis(); mHandler.post(() -> { try { + if (mFlags.improveWakelockLatency()) { + if (isEnabled) { + mWakeLockLog.onWakeLockAcquired(tag, ownerUid, flags, currentTime); + } else { + mWakeLockLog.onWakeLockReleased(tag, ownerUid, currentTime); + } + } callback.onStateChanged(isEnabled); } catch (RemoteException e) { Slog.e(TAG, "Wakelock.mCallback [" + tag + "] is already dead.", e); @@ -1057,6 +1074,7 @@ public class Notifier { public NotifierHandler(Looper looper) { super(looper, null, true /*async*/); } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -1085,4 +1103,28 @@ public class Notifier { } } } + + public interface Injector { + /** + * Gets the current time in millis + */ + long currentTimeMillis(); + + /** + * Gets the WakeLockLog object + */ + WakeLockLog getWakeLockLog(Context context); + } + + static class RealInjector implements Injector { + @Override + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + + @Override + public WakeLockLog getWakeLockLog(Context context) { + return new WakeLockLog(context); + } + } } diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 76cedd8e5e60..ce0120c245d6 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -988,10 +988,10 @@ public final class PowerManagerService extends SystemService Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, SuspendBlocker suspendBlocker, WindowManagerPolicy policy, FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector, - Executor backgroundExecutor) { + Executor backgroundExecutor, PowerManagerFlags powerManagerFlags) { return new Notifier( looper, context, batteryStats, suspendBlocker, policy, faceDownDetector, - screenUndimDetector, backgroundExecutor); + screenUndimDetector, backgroundExecutor, powerManagerFlags, /*injector=*/ null); } SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) { @@ -1373,7 +1373,7 @@ public final class PowerManagerService extends SystemService mNotifier = mInjector.createNotifier(Looper.getMainLooper(), mContext, mBatteryStats, mInjector.createSuspendBlocker(this, "PowerManagerService.Broadcasts"), mPolicy, mFaceDownDetector, mScreenUndimDetector, - BackgroundThread.getExecutor()); + BackgroundThread.getExecutor(), mFeatureFlags); mPowerGroups.append(Display.DEFAULT_DISPLAY_GROUP, new PowerGroup(WAKEFULNESS_AWAKE, mPowerGroupWakefulnessChangeListener, diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java index b131311f76c7..968ff59ad8fc 100644 --- a/services/core/java/com/android/server/power/WakeLockLog.java +++ b/services/core/java/com/android/server/power/WakeLockLog.java @@ -154,9 +154,10 @@ final class WakeLockLog { * @param tag The wake lock tag * @param ownerUid The owner UID of the wake lock. * @param flags Flags used for the wake lock. + * @param eventTime The time at which the event occurred */ - public void onWakeLockAcquired(String tag, int ownerUid, int flags) { - onWakeLockEvent(TYPE_ACQUIRE, tag, ownerUid, flags); + public void onWakeLockAcquired(String tag, int ownerUid, int flags, long eventTime) { + onWakeLockEvent(TYPE_ACQUIRE, tag, ownerUid, flags, eventTime); } /** @@ -164,9 +165,10 @@ final class WakeLockLog { * * @param tag The wake lock tag * @param ownerUid The owner UID of the wake lock. + * @param eventTime The time at which the event occurred */ - public void onWakeLockReleased(String tag, int ownerUid) { - onWakeLockEvent(TYPE_RELEASE, tag, ownerUid, 0 /* flags */); + public void onWakeLockReleased(String tag, int ownerUid, long eventTime) { + onWakeLockEvent(TYPE_RELEASE, tag, ownerUid, 0 /* flags */, eventTime); } /** @@ -242,9 +244,10 @@ final class WakeLockLog { * @param tag The wake lock's identifying tag. * @param ownerUid The owner UID of the wake lock. * @param flags The flags used with the wake lock. + * @param eventTime The time at which the event occurred */ private void onWakeLockEvent(int eventType, String tag, int ownerUid, - int flags) { + int flags, long eventTime) { if (tag == null) { Slog.w(TAG, "Insufficient data to log wakelock [tag: " + tag + ", ownerUid: " + ownerUid @@ -252,7 +255,8 @@ final class WakeLockLog { return; } - final long time = mInjector.currentTimeMillis(); + final long time = (eventTime == -1) ? mInjector.currentTimeMillis() : eventTime; + final int translatedFlags = eventType == TYPE_ACQUIRE ? translateFlagsFromPowerManager(flags) : 0; diff --git a/services/core/java/com/android/server/power/batterysaver/flags.aconfig b/services/core/java/com/android/server/power/batterysaver/flags.aconfig index fa29dc19b555..1dea5239dbe5 100644 --- a/services/core/java/com/android/server/power/batterysaver/flags.aconfig +++ b/services/core/java/com/android/server/power/batterysaver/flags.aconfig @@ -3,7 +3,7 @@ container: "system" flag { name: "update_auto_turn_on_notification_string_and_action" - namespace: "battery_saver" + namespace: "backstage_power" description: "Improve the string and hightligh settings item for battery saver auto-turn-on notification" bug: "336960905" metadata { diff --git a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java index a5a7069b6ea1..ff1d2e402350 100644 --- a/services/core/java/com/android/server/power/feature/PowerManagerFlags.java +++ b/services/core/java/com/android/server/power/feature/PowerManagerFlags.java @@ -35,18 +35,31 @@ public class PowerManagerFlags { Flags.FLAG_ENABLE_EARLY_SCREEN_TIMEOUT_DETECTOR, Flags::enableEarlyScreenTimeoutDetector); + private final FlagState mImproveWakelockLatency = new FlagState( + Flags.FLAG_IMPROVE_WAKELOCK_LATENCY, + Flags::improveWakelockLatency + ); + /** Returns whether early-screen-timeout-detector is enabled on not. */ public boolean isEarlyScreenTimeoutDetectorEnabled() { return mEarlyScreenTimeoutDetectorFlagState.isEnabled(); } /** + * @return Whether to improve the wakelock acquire/release latency or not + */ + public boolean improveWakelockLatency() { + return mImproveWakelockLatency.isEnabled(); + } + + /** * dumps all flagstates * @param pw printWriter */ public void dump(PrintWriter pw) { pw.println("PowerManagerFlags:"); pw.println(" " + mEarlyScreenTimeoutDetectorFlagState); + pw.println(" " + mImproveWakelockLatency); } private static class FlagState { diff --git a/services/core/java/com/android/server/power/feature/power_flags.aconfig b/services/core/java/com/android/server/power/feature/power_flags.aconfig index ca58153cf25b..3581b2fad1df 100644 --- a/services/core/java/com/android/server/power/feature/power_flags.aconfig +++ b/services/core/java/com/android/server/power/feature/power_flags.aconfig @@ -10,3 +10,11 @@ flag { bug: "309861917" is_fixed_read_only: true } + +flag { + name: "improve_wakelock_latency" + namespace: "power" + description: "Feature flag for tracking the optimizations to improve the latency of acquiring and releasing a wakelock." + bug: "339590565" + is_fixed_read_only: true +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java index 4ddd54623e8c..267ddd07fd32 100644 --- a/services/core/java/com/android/server/power/hint/HintManagerService.java +++ b/services/core/java/com/android/server/power/hint/HintManagerService.java @@ -16,6 +16,8 @@ package com.android.server.power.hint; +import static android.os.Flags.adpfUseFmqChannel; + import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; import static com.android.server.power.hint.Flags.powerhintThreadCleanup; @@ -26,6 +28,8 @@ import android.app.ActivityManagerInternal; import android.app.StatsManager; import android.app.UidObserver; import android.content.Context; +import android.hardware.power.ChannelConfig; +import android.hardware.power.IPower; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; import android.hardware.power.WorkDuration; @@ -39,6 +43,7 @@ import android.os.Message; import android.os.PerformanceHintManager; import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; @@ -69,6 +74,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; @@ -91,9 +97,21 @@ public final class HintManagerService extends SystemService { @GuardedBy("mLock") private final ArrayMap<Integer, ArrayMap<IBinder, ArraySet<AppHintSession>>> mActiveSessions; + // Multi-level map storing all the channel binder token death listeners. + // First level is keyed by the UID of the client process owning the channel. + // Second level is the tgid of the process, which will often just be size one. + // Each channel is unique per (tgid, uid) pair, so this map associates each pair with an + // object that listens for the death notification of the binder token that was provided by + // that client when it created the channel, so we can detect when the client process dies. + @GuardedBy("mChannelMapLock") + private ArrayMap<Integer, TreeMap<Integer, ChannelItem>> mChannelMap; + /** Lock to protect mActiveSessions and the UidObserver. */ private final Object mLock = new Object(); + /** Lock to protect mChannelMap. */ + private final Object mChannelMapLock = new Object(); + @GuardedBy("mNonIsolatedTidsLock") private final Map<Integer, Set<Long>> mNonIsolatedTids; @@ -110,6 +128,9 @@ public final class HintManagerService extends SystemService { private AtomicBoolean mConfigCreationSupport = new AtomicBoolean(true); + private final IPower mPowerHal; + private int mPowerHalVersion; + private static final String PROPERTY_SF_ENABLE_CPU_HINT = "debug.sf.enable_adpf_cpu_hint"; private static final String PROPERTY_HWUI_ENABLE_HINT_MANAGER = "debug.hwui.use_hint_manager"; @@ -131,12 +152,22 @@ public final class HintManagerService extends SystemService { mNonIsolatedTids = null; } mActiveSessions = new ArrayMap<>(); + mChannelMap = new ArrayMap<>(); mNativeWrapper = injector.createNativeWrapper(); mNativeWrapper.halInit(); mHintSessionPreferredRate = mNativeWrapper.halGetHintSessionPreferredRate(); mUidObserver = new MyUidObserver(); mAmInternal = Objects.requireNonNull( LocalServices.getService(ActivityManagerInternal.class)); + mPowerHal = injector.createIPower(); + mPowerHalVersion = 0; + if (mPowerHal != null) { + try { + mPowerHalVersion = mPowerHal.getInterfaceVersion(); + } catch (RemoteException e) { + throw new IllegalStateException("Could not contact PowerHAL!", e); + } + } } private ServiceThread createCleanUpThread() { @@ -151,6 +182,10 @@ public final class HintManagerService extends SystemService { NativeWrapper createNativeWrapper() { return new NativeWrapper(); } + IPower createIPower() { + return IPower.Stub.asInterface( + ServiceManager.waitForDeclaredService(IPower.DESCRIPTOR + "/default")); + } } private boolean isHalSupported() { @@ -344,6 +379,16 @@ public final class HintManagerService extends SystemService { } } } + synchronized (mChannelMapLock) { + // Clean up the uid's session channels + final TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid); + if (uidMap != null) { + for (Map.Entry<Integer, ChannelItem> entry : uidMap.entrySet()) { + entry.getValue().closeChannel(); + } + mChannelMap.remove(uid); + } + } }); } @@ -383,6 +428,113 @@ public final class HintManagerService extends SystemService { } } + /** + * Creates a channel item in the channel map if one does not exist, then returns + * the entry in the channel map. + */ + public ChannelItem getOrCreateMappedChannelItem(int tgid, int uid, IBinder token) { + synchronized (mChannelMapLock) { + if (!mChannelMap.containsKey(uid)) { + mChannelMap.put(uid, new TreeMap<Integer, ChannelItem>()); + } + TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid); + if (!map.containsKey(tgid)) { + ChannelItem item = new ChannelItem(tgid, uid, token); + item.openChannel(); + map.put(tgid, item); + } + return map.get(tgid); + } + } + + /** + * This removes an entry in the binder token callback map when a channel is closed, + * and unregisters its callbacks. + */ + public void removeChannelItem(Integer tgid, Integer uid) { + synchronized (mChannelMapLock) { + TreeMap<Integer, ChannelItem> map = mChannelMap.get(uid); + if (map != null) { + ChannelItem item = map.get(tgid); + if (item != null) { + item.closeChannel(); + map.remove(tgid); + } + if (map.isEmpty()) { + mChannelMap.remove(uid); + } + } + } + } + + /** + * Manages the lifecycle of a single channel. This includes caching the channel descriptor, + * receiving binder token death notifications, and handling cleanup on uid termination. There + * can only be one ChannelItem per (tgid, uid) pair in mChannelMap, and channel creation happens + * when a ChannelItem enters the map, while destruction happens when it leaves the map. + */ + private class ChannelItem implements IBinder.DeathRecipient { + @Override + public void binderDied() { + removeChannelItem(mTgid, mUid); + } + + ChannelItem(int tgid, int uid, IBinder token) { + this.mTgid = tgid; + this.mUid = uid; + this.mToken = token; + this.mLinked = false; + this.mConfig = null; + } + + public void closeChannel() { + if (mLinked) { + mToken.unlinkToDeath(this, 0); + mLinked = false; + } + if (mConfig != null) { + try { + mPowerHal.closeSessionChannel(mTgid, mUid); + } catch (RemoteException e) { + throw new IllegalStateException("Failed to close session channel!", e); + } + mConfig = null; + } + } + + public void openChannel() { + if (!mLinked) { + try { + mToken.linkToDeath(this, 0); + } catch (RemoteException e) { + throw new IllegalStateException("Client already dead", e); + } + mLinked = true; + } + if (mConfig == null) { + try { + // This method uses PowerHAL directly through the SDK, + // to avoid needing to pass the ChannelConfig through JNI. + mConfig = mPowerHal.getSessionChannel(mTgid, mUid); + } catch (RemoteException e) { + removeChannelItem(mTgid, mUid); + throw new IllegalStateException("Failed to create session channel!", e); + } + } + } + + ChannelConfig getConfig() { + return mConfig; + } + + // To avoid accidental double-linking / unlinking + boolean mLinked; + final int mTgid; + final int mUid; + final IBinder mToken; + ChannelConfig mConfig; + } + final class CleanUpHandler extends Handler { // status of processed tid used for caching private static final int TID_NOT_CHECKED = 0; @@ -570,6 +722,18 @@ public final class HintManagerService extends SystemService { return mService; } + @VisibleForTesting + Boolean hasChannel(int tgid, int uid) { + synchronized (mChannelMapLock) { + TreeMap<Integer, ChannelItem> uidMap = mChannelMap.get(uid); + if (uidMap != null) { + ChannelItem item = uidMap.get(tgid); + return item != null; + } + return false; + } + } + // returns the first invalid tid or null if not found private Integer checkTidValid(int uid, int tgid, int [] tids, IntArray nonIsolated) { // Make sure all tids belongs to the same UID (including isolated UID), @@ -710,6 +874,28 @@ public final class HintManagerService extends SystemService { } @Override + public ChannelConfig getSessionChannel(IBinder token) { + if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) { + return null; + } + java.util.Objects.requireNonNull(token); + final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); + final int callingUid = Binder.getCallingUid(); + ChannelItem item = getOrCreateMappedChannelItem(callingTgid, callingUid, token); + return item.getConfig(); + }; + + @Override + public void closeSessionChannel() { + if (mPowerHalVersion < 5 || !adpfUseFmqChannel()) { + return; + } + final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid()); + final int callingUid = Binder.getCallingUid(); + removeChannelItem(callingTgid, callingUid); + }; + + @Override public long getHintSessionPreferredRate() { return mHintSessionPreferredRate; } diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java index 5aad570ffd41..884c26ca3c00 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -19,12 +19,9 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.os.BatteryConsumer; -import com.android.internal.os.PowerStats; - import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -206,25 +203,9 @@ public class AggregatedPowerStatsConfig { return mPowerComponents; } - private static final PowerStatsProcessor NO_OP_PROCESSOR = - new PowerStatsProcessor() { - @Override - void finish(PowerComponentAggregatedPowerStats stats) { - } - - @Override - String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - return Arrays.toString(stats); - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - return descriptor.getStateLabel(key) + " " + Arrays.toString(stats); - } - - @Override - String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - return Arrays.toString(stats); - } - }; + private static final PowerStatsProcessor NO_OP_PROCESSOR = new PowerStatsProcessor() { + @Override + void finish(PowerComponentAggregatedPowerStats stats) { + } + }; } diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java index cb10da9787df..2f1641980784 100644 --- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java +++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java @@ -572,34 +572,41 @@ public class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStat } if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_BT) != 0) { - // We were asked to fetch Bluetooth data. - final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null) { - SynchronousResultReceiver resultReceiver = - new SynchronousResultReceiver("bluetooth"); - adapter.requestControllerActivityEnergyInfo( - Runnable::run, - new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() { - @Override - public void onBluetoothActivityEnergyInfoAvailable( - BluetoothActivityEnergyInfo info) { - Bundle bundle = new Bundle(); - bundle.putParcelable( - BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info); - resultReceiver.send(0, bundle); - } + @SuppressWarnings("GuardedBy") + PowerStatsCollector collector = mStats.getPowerStatsCollector( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH); + if (collector.isEnabled()) { + collector.schedule(); + } else { + // We were asked to fetch Bluetooth data. + final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter != null) { + SynchronousResultReceiver resultReceiver = + new SynchronousResultReceiver("bluetooth"); + adapter.requestControllerActivityEnergyInfo( + Runnable::run, + new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() { + @Override + public void onBluetoothActivityEnergyInfoAvailable( + BluetoothActivityEnergyInfo info) { + Bundle bundle = new Bundle(); + bundle.putParcelable( + BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info); + resultReceiver.send(0, bundle); + } - @Override - public void onBluetoothActivityEnergyInfoError(int errorCode) { - Slog.w(TAG, "error reading Bluetooth stats: " + errorCode); - Bundle bundle = new Bundle(); - bundle.putParcelable( - BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, null); - resultReceiver.send(0, bundle); + @Override + public void onBluetoothActivityEnergyInfoError(int errorCode) { + Slog.w(TAG, "error reading Bluetooth stats: " + errorCode); + Bundle bundle = new Bundle(); + bundle.putParcelable( + BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, null); + resultReceiver.send(0, bundle); + } } - } - ); - bluetoothReceiver = resultReceiver; + ); + bluetoothReceiver = resultReceiver; + } } } diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 49c4000d7308..1b6af7170756 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -32,6 +32,8 @@ import android.app.ActivityManager; import android.app.AlarmManager; import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothActivityEnergyInfo; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; import android.bluetooth.UidTraffic; import android.content.BroadcastReceiver; import android.content.ContentResolver; @@ -172,6 +174,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; +import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -294,6 +297,7 @@ public class BatteryStatsImpl extends BatteryStats { private final CpuPowerStatsCollector mCpuPowerStatsCollector; private final MobileRadioPowerStatsCollector mMobileRadioPowerStatsCollector; private final WifiPowerStatsCollector mWifiPowerStatsCollector; + private final BluetoothPowerStatsCollector mBluetoothPowerStatsCollector; private final SparseBooleanArray mPowerStatsCollectorEnabled = new SparseBooleanArray(); private final WifiPowerStatsCollector.WifiStatsRetriever mWifiStatsRetriever = new WifiPowerStatsCollector.WifiStatsRetriever() { @@ -313,6 +317,38 @@ public class BatteryStatsImpl extends BatteryStats { } }; + private class BluetoothStatsRetrieverImpl implements + BluetoothPowerStatsCollector.BluetoothStatsRetriever { + private final BluetoothManager mBluetoothManager; + + BluetoothStatsRetrieverImpl(BluetoothManager bluetoothManager) { + mBluetoothManager = bluetoothManager; + } + + @Override + public void retrieveBluetoothScanTimes(Callback callback) { + synchronized (BatteryStatsImpl.this) { + retrieveBluetoothScanTimesLocked(callback); + } + } + + @Override + public boolean requestControllerActivityEnergyInfo(Executor executor, + BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback) { + if (mBluetoothManager == null) { + return false; + } + + BluetoothAdapter adapter = mBluetoothManager.getAdapter(); + if (adapter == null) { + return false; + } + + adapter.requestControllerActivityEnergyInfo(executor, callback); + return true; + } + } + public LongSparseArray<SamplingTimer> getKernelMemoryStats() { return mKernelMemoryStats; } @@ -463,11 +499,17 @@ public class BatteryStatsImpl extends BatteryStats { public static class BatteryStatsConfig { static final int RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG = 1 << 0; static final int RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG = 1 << 1; - static final long DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD = - TimeUnit.HOURS.toMillis(1); private final int mFlags; - private SparseLongArray mPowerStatsThrottlePeriods; + private final Long mDefaultPowerStatsThrottlePeriod; + private final Map<String, Long> mPowerStatsThrottlePeriods; + + @VisibleForTesting + public BatteryStatsConfig() { + mFlags = 0; + mDefaultPowerStatsThrottlePeriod = 0L; + mPowerStatsThrottlePeriods = Map.of(); + } private BatteryStatsConfig(Builder builder) { int flags = 0; @@ -478,6 +520,7 @@ public class BatteryStatsImpl extends BatteryStats { flags |= RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG; } mFlags = flags; + mDefaultPowerStatsThrottlePeriod = builder.mDefaultPowerStatsThrottlePeriod; mPowerStatsThrottlePeriods = builder.mPowerStatsThrottlePeriods; } @@ -485,7 +528,7 @@ public class BatteryStatsImpl extends BatteryStats { * Returns whether a BatteryStats reset should occur on unplug when the battery level is * high. */ - boolean shouldResetOnUnplugHighBatteryLevel() { + public boolean shouldResetOnUnplugHighBatteryLevel() { return (mFlags & RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG) == RESET_ON_UNPLUG_HIGH_BATTERY_LEVEL_FLAG; } @@ -494,14 +537,18 @@ public class BatteryStatsImpl extends BatteryStats { * Returns whether a BatteryStats reset should occur on unplug if the battery charge a * significant amount since it has been plugged in. */ - boolean shouldResetOnUnplugAfterSignificantCharge() { + public boolean shouldResetOnUnplugAfterSignificantCharge() { return (mFlags & RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG) == RESET_ON_UNPLUG_AFTER_SIGNIFICANT_CHARGE_FLAG; } - long getPowerStatsThrottlePeriod(@BatteryConsumer.PowerComponent int powerComponent) { - return mPowerStatsThrottlePeriods.get(powerComponent, - DEFAULT_POWER_STATS_COLLECTION_THROTTLE_PERIOD); + /** + * Returns the minimum amount of time (in millis) to wait between passes + * of power stats collection for the specified power component. + */ + public long getPowerStatsThrottlePeriod(String powerComponentName) { + return mPowerStatsThrottlePeriods.getOrDefault(powerComponentName, + mDefaultPowerStatsThrottlePeriod); } /** @@ -510,18 +557,19 @@ public class BatteryStatsImpl extends BatteryStats { public static class Builder { private boolean mResetOnUnplugHighBatteryLevel; private boolean mResetOnUnplugAfterSignificantCharge; - private SparseLongArray mPowerStatsThrottlePeriods; + public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD = + TimeUnit.HOURS.toMillis(1); + public static final long DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU = + TimeUnit.MINUTES.toMillis(1); + private long mDefaultPowerStatsThrottlePeriod = DEFAULT_POWER_STATS_THROTTLE_PERIOD; + private final Map<String, Long> mPowerStatsThrottlePeriods = new HashMap<>(); public Builder() { mResetOnUnplugHighBatteryLevel = true; mResetOnUnplugAfterSignificantCharge = true; - mPowerStatsThrottlePeriods = new SparseLongArray(); - setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU, - TimeUnit.MINUTES.toMillis(1)); - setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, - TimeUnit.HOURS.toMillis(1)); - setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_WIFI, - TimeUnit.HOURS.toMillis(1)); + setPowerStatsThrottlePeriodMillis(BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_CPU), + DEFAULT_POWER_STATS_THROTTLE_PERIOD_CPU); } /** @@ -553,9 +601,18 @@ public class BatteryStatsImpl extends BatteryStats { * Sets the minimum amount of time (in millis) to wait between passes * of power stats collection for the specified power component. */ - public Builder setPowerStatsThrottlePeriodMillis( - @BatteryConsumer.PowerComponent int powerComponent, long periodMs) { - mPowerStatsThrottlePeriods.put(powerComponent, periodMs); + public Builder setPowerStatsThrottlePeriodMillis(String powerComponentName, + long periodMs) { + mPowerStatsThrottlePeriods.put(powerComponentName, periodMs); + return this; + } + + /** + * Sets the minimum amount of time (in millis) to wait between passes + * of power stats collection for any components not configured explicitly. + */ + public Builder setDefaultPowerStatsThrottlePeriodMillis(long periodMs) { + mDefaultPowerStatsThrottlePeriod = periodMs; return this; } } @@ -1586,8 +1643,7 @@ public class BatteryStatsImpl extends BatteryStats { protected final Constants mConstants; @VisibleForTesting - @GuardedBy("this") - protected BatteryStatsConfig mBatteryStatsConfig; + protected final BatteryStatsConfig mBatteryStatsConfig; @GuardedBy("this") private AlarmManager mAlarmManager = null; @@ -1906,12 +1962,14 @@ public class BatteryStatsImpl extends BatteryStats { } private class PowerStatsCollectorInjector implements CpuPowerStatsCollector.Injector, - MobileRadioPowerStatsCollector.Injector, WifiPowerStatsCollector.Injector { + MobileRadioPowerStatsCollector.Injector, WifiPowerStatsCollector.Injector, + BluetoothPowerStatsCollector.Injector { private PackageManager mPackageManager; private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; private NetworkStatsManager mNetworkStatsManager; private TelephonyManager mTelephonyManager; private WifiManager mWifiManager; + private BluetoothPowerStatsCollector.BluetoothStatsRetriever mBluetoothStatsRetriever; void setContext(Context context) { mPackageManager = context.getPackageManager(); @@ -1920,6 +1978,8 @@ public class BatteryStatsImpl extends BatteryStats { mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class); mTelephonyManager = context.getSystemService(TelephonyManager.class); mWifiManager = context.getSystemService(WifiManager.class); + mBluetoothStatsRetriever = new BluetoothStatsRetrieverImpl( + context.getSystemService(BluetoothManager.class)); } @Override @@ -1933,6 +1993,11 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return mBatteryStatsConfig.getPowerStatsThrottlePeriod(powerComponentName); + } + + @Override public PowerStatsUidResolver getUidResolver() { return mPowerStatsUidResolver; } @@ -1993,6 +2058,11 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + public BluetoothPowerStatsCollector.BluetoothStatsRetriever getBluetoothStatsRetriever() { + return mBluetoothStatsRetriever; + } + + @Override public LongSupplier getCallDurationSupplier() { return () -> mPhoneOnTimer.getTotalTimeLocked(mClock.elapsedRealtime() * 1000, STATS_SINCE_CHARGED); @@ -6749,6 +6819,24 @@ public class BatteryStatsImpl extends BatteryStats { } } + private void retrieveBluetoothScanTimesLocked( + BluetoothPowerStatsCollector.BluetoothStatsRetriever.Callback callback) { + long elapsedTimeUs = mClock.elapsedRealtime() * 1000; + for (int i = mUidStats.size() - 1; i >= 0; i--) { + Uid uidStats = mUidStats.valueAt(i); + if (uidStats.mBluetoothScanTimer == null) { + continue; + } + + long scanTimeUs = mBluetoothScanTimer.getTotalTimeLocked(elapsedTimeUs, + STATS_SINCE_CHARGED); + if (scanTimeUs != 0) { + int uid = mUidStats.keyAt(i); + callback.onBluetoothScanTime(uid, (scanTimeUs + 500) / 1000); + } + } + } + @GuardedBy("this") private void noteWifiRadioApWakeupLocked(final long elapsedRealtimeMillis, final long uptimeMillis, int uid) { @@ -11167,21 +11255,20 @@ public class BatteryStatsImpl extends BatteryStats { mConstants.MAX_HISTORY_FILES, mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock, mMonotonicClock, traceDelegate, eventLogger); - mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector, - mBatteryStatsConfig.getPowerStatsThrottlePeriod( - BatteryConsumer.POWER_COMPONENT_CPU)); + mCpuPowerStatsCollector = new CpuPowerStatsCollector(mPowerStatsCollectorInjector); mCpuPowerStatsCollector.addConsumer(this::recordPowerStats); mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector( - mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod( - BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)); + mPowerStatsCollectorInjector); mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats); - mWifiPowerStatsCollector = new WifiPowerStatsCollector( - mPowerStatsCollectorInjector, mBatteryStatsConfig.getPowerStatsThrottlePeriod( - BatteryConsumer.POWER_COMPONENT_WIFI)); + mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector); mWifiPowerStatsCollector.addConsumer(this::recordPowerStats); + mBluetoothPowerStatsCollector = new BluetoothPowerStatsCollector( + mPowerStatsCollectorInjector); + mBluetoothPowerStatsCollector.addConsumer(this::recordPowerStats); + mStartCount++; initTimersAndCounters(); mOnBattery = mOnBatteryInternal = false; @@ -13126,6 +13213,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void updateBluetoothStateLocked(@Nullable final BluetoothActivityEnergyInfo info, final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs) { + if (mBluetoothPowerStatsCollector.isEnabled()) { + return; + } + if (DEBUG_ENERGY) { Slog.d(TAG, "Updating bluetooth stats: " + info); } @@ -13133,6 +13224,7 @@ public class BatteryStatsImpl extends BatteryStats { if (info == null) { return; } + if (!mOnBatteryInternal || mIgnoreNextExternalStats) { mLastBluetoothActivityInfo.set(info); return; @@ -13167,7 +13259,6 @@ public class BatteryStatsImpl extends BatteryStats { (mGlobalEnergyConsumerStats != null && mBluetoothPowerCalculator != null && consumedChargeUC > 0) ? new SparseDoubleArray() : null; - long totalScanTimeMs = 0; final int uidCount = mUidStats.size(); @@ -14596,6 +14687,10 @@ public class BatteryStatsImpl extends BatteryStats { mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_WIFI)); mWifiPowerStatsCollector.schedule(); + mBluetoothPowerStatsCollector.setEnabled( + mPowerStatsCollectorEnabled.get(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)); + mBluetoothPowerStatsCollector.schedule(); + mSystemReady = true; } @@ -14612,6 +14707,8 @@ public class BatteryStatsImpl extends BatteryStats { return mMobileRadioPowerStatsCollector; case BatteryConsumer.POWER_COMPONENT_WIFI: return mWifiPowerStatsCollector; + case BatteryConsumer.POWER_COMPONENT_BLUETOOTH: + return mBluetoothPowerStatsCollector; } return null; } @@ -16148,6 +16245,7 @@ public class BatteryStatsImpl extends BatteryStats { mCpuPowerStatsCollector.forceSchedule(); mMobileRadioPowerStatsCollector.forceSchedule(); mWifiPowerStatsCollector.forceSchedule(); + mBluetoothPowerStatsCollector.forceSchedule(); } /** @@ -16167,6 +16265,7 @@ public class BatteryStatsImpl extends BatteryStats { mCpuPowerStatsCollector.collectAndDump(pw); mMobileRadioPowerStatsCollector.collectAndDump(pw); mWifiPowerStatsCollector.collectAndDump(pw); + mBluetoothPowerStatsCollector.collectAndDump(pw); } private final Runnable mWriteAsyncRunnable = () -> { diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index 0d5eabc5ed47..b25239574071 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -90,7 +90,9 @@ public class BatteryUsageStatsProvider { if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_WIFI)) { mPowerCalculators.add(new WifiPowerCalculator(mPowerProfile)); } - mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile)); + if (!mPowerStatsExporterEnabled.get(BatteryConsumer.POWER_COMPONENT_BLUETOOTH)) { + mPowerCalculators.add(new BluetoothPowerCalculator(mPowerProfile)); + } mPowerCalculators.add(new SensorPowerCalculator( mContext.getSystemService(SensorManager.class))); mPowerCalculators.add(new GnssPowerCalculator(mPowerProfile)); diff --git a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java new file mode 100644 index 000000000000..8a5085b0b34b --- /dev/null +++ b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsCollector.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.power.stats; + +import android.bluetooth.BluetoothActivityEnergyInfo; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.UidTraffic; +import android.content.pm.PackageManager; +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.Handler; +import android.os.PersistableBundle; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.function.IntSupplier; + +public class BluetoothPowerStatsCollector extends PowerStatsCollector { + private static final String TAG = "BluetoothPowerStatsCollector"; + + private static final long BLUETOOTH_ACTIVITY_REQUEST_TIMEOUT = 20000; + + private static final long ENERGY_UNSPECIFIED = -1; + + interface BluetoothStatsRetriever { + interface Callback { + void onBluetoothScanTime(int uid, long scanTimeMs); + } + + void retrieveBluetoothScanTimes(Callback callback); + + boolean requestControllerActivityEnergyInfo(Executor executor, + BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback); + } + + interface Injector { + Handler getHandler(); + Clock getClock(); + PowerStatsUidResolver getUidResolver(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); + PackageManager getPackageManager(); + ConsumedEnergyRetriever getConsumedEnergyRetriever(); + IntSupplier getVoltageSupplier(); + BluetoothStatsRetriever getBluetoothStatsRetriever(); + } + + private final Injector mInjector; + + private BluetoothPowerStatsLayout mLayout; + private boolean mIsInitialized; + private PowerStats mPowerStats; + private long[] mDeviceStats; + private BluetoothStatsRetriever mBluetoothStatsRetriever; + private ConsumedEnergyRetriever mConsumedEnergyRetriever; + private IntSupplier mVoltageSupplier; + private int[] mEnergyConsumerIds = new int[0]; + private long[] mLastConsumedEnergyUws; + private int mLastVoltageMv; + + private long mLastRxTime; + private long mLastTxTime; + private long mLastIdleTime; + + private static class UidStats { + public long rxCount; + public long lastRxCount; + public long txCount; + public long lastTxCount; + public long scanTime; + public long lastScanTime; + } + + private final SparseArray<UidStats> mUidStats = new SparseArray<>(); + + BluetoothPowerStatsCollector(Injector injector) { + super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH)), + injector.getUidResolver(), + injector.getClock()); + mInjector = injector; + } + + @Override + public void setEnabled(boolean enabled) { + if (enabled) { + PackageManager packageManager = mInjector.getPackageManager(); + super.setEnabled(packageManager != null + && packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)); + } else { + super.setEnabled(false); + } + } + + private boolean ensureInitialized() { + if (mIsInitialized) { + return true; + } + + if (!isEnabled()) { + return false; + } + + mConsumedEnergyRetriever = mInjector.getConsumedEnergyRetriever(); + mVoltageSupplier = mInjector.getVoltageSupplier(); + mBluetoothStatsRetriever = mInjector.getBluetoothStatsRetriever(); + mEnergyConsumerIds = + mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH); + mLastConsumedEnergyUws = new long[mEnergyConsumerIds.length]; + Arrays.fill(mLastConsumedEnergyUws, ENERGY_UNSPECIFIED); + + mLayout = new BluetoothPowerStatsLayout(); + mLayout.addDeviceBluetoothControllerActivity(); + mLayout.addDeviceSectionEnergyConsumers(mEnergyConsumerIds.length); + mLayout.addDeviceSectionUsageDuration(); + mLayout.addDeviceSectionPowerEstimate(); + mLayout.addUidTrafficStats(); + mLayout.addUidSectionPowerEstimate(); + + PersistableBundle extras = new PersistableBundle(); + mLayout.toExtras(extras); + PowerStats.Descriptor powerStatsDescriptor = new PowerStats.Descriptor( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH, mLayout.getDeviceStatsArrayLength(), + null, 0, mLayout.getUidStatsArrayLength(), + extras); + mPowerStats = new PowerStats(powerStatsDescriptor); + mDeviceStats = mPowerStats.stats; + + mIsInitialized = true; + return true; + } + + @Override + protected PowerStats collectStats() { + if (!ensureInitialized()) { + return null; + } + + mPowerStats.uidStats.clear(); + + collectBluetoothActivityInfo(); + collectBluetoothScanStats(); + + if (mEnergyConsumerIds.length != 0) { + collectEnergyConsumers(); + } + + return mPowerStats; + } + + private void collectBluetoothActivityInfo() { + CompletableFuture<BluetoothActivityEnergyInfo> immediateFuture = new CompletableFuture<>(); + boolean success = mBluetoothStatsRetriever.requestControllerActivityEnergyInfo( + Runnable::run, + new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() { + @Override + public void onBluetoothActivityEnergyInfoAvailable( + BluetoothActivityEnergyInfo info) { + immediateFuture.complete(info); + } + + @Override + public void onBluetoothActivityEnergyInfoError(int error) { + immediateFuture.completeExceptionally( + new RuntimeException("error: " + error)); + } + }); + + if (!success) { + return; + } + + BluetoothActivityEnergyInfo activityInfo; + try { + activityInfo = immediateFuture.get(BLUETOOTH_ACTIVITY_REQUEST_TIMEOUT, + TimeUnit.MILLISECONDS); + } catch (Exception e) { + Slog.e(TAG, "Cannot acquire BluetoothActivityEnergyInfo", e); + activityInfo = null; + } + + if (activityInfo == null) { + return; + } + + long rxTime = activityInfo.getControllerRxTimeMillis(); + long rxTimeDelta = Math.max(0, rxTime - mLastRxTime); + mLayout.setDeviceRxTime(mDeviceStats, rxTimeDelta); + mLastRxTime = rxTime; + + long txTime = activityInfo.getControllerTxTimeMillis(); + long txTimeDelta = Math.max(0, txTime - mLastTxTime); + mLayout.setDeviceTxTime(mDeviceStats, txTimeDelta); + mLastTxTime = txTime; + + long idleTime = activityInfo.getControllerIdleTimeMillis(); + long idleTimeDelta = Math.max(0, idleTime - mLastIdleTime); + mLayout.setDeviceIdleTime(mDeviceStats, idleTimeDelta); + mLastIdleTime = idleTime; + + mPowerStats.durationMs = rxTimeDelta + txTimeDelta + idleTimeDelta; + + List<UidTraffic> uidTraffic = activityInfo.getUidTraffic(); + for (int i = uidTraffic.size() - 1; i >= 0; i--) { + UidTraffic ut = uidTraffic.get(i); + int uid = mUidResolver.mapUid(ut.getUid()); + UidStats counts = mUidStats.get(uid); + if (counts == null) { + counts = new UidStats(); + mUidStats.put(uid, counts); + } + counts.rxCount += ut.getRxBytes(); + counts.txCount += ut.getTxBytes(); + } + + for (int i = mUidStats.size() - 1; i >= 0; i--) { + UidStats counts = mUidStats.valueAt(i); + long rxDelta = Math.max(0, counts.rxCount - counts.lastRxCount); + counts.lastRxCount = counts.rxCount; + counts.rxCount = 0; + + long txDelta = Math.max(0, counts.txCount - counts.lastTxCount); + counts.lastTxCount = counts.txCount; + counts.txCount = 0; + + if (rxDelta != 0 || txDelta != 0) { + int uid = mUidStats.keyAt(i); + long[] stats = mPowerStats.uidStats.get(uid); + if (stats == null) { + stats = new long[mLayout.getUidStatsArrayLength()]; + mPowerStats.uidStats.put(uid, stats); + } + + mLayout.setUidRxBytes(stats, rxDelta); + mLayout.setUidTxBytes(stats, txDelta); + } + } + } + + private void collectBluetoothScanStats() { + mBluetoothStatsRetriever.retrieveBluetoothScanTimes((uid, scanTimeMs) -> { + uid = mUidResolver.mapUid(uid); + UidStats uidStats = mUidStats.get(uid); + if (uidStats == null) { + uidStats = new UidStats(); + mUidStats.put(uid, uidStats); + } + uidStats.scanTime += scanTimeMs; + }); + + long totalScanTime = 0; + for (int i = mUidStats.size() - 1; i >= 0; i--) { + UidStats counts = mUidStats.valueAt(i); + if (counts.scanTime == 0) { + continue; + } + + long delta = Math.max(0, counts.scanTime - counts.lastScanTime); + counts.lastScanTime = counts.scanTime; + counts.scanTime = 0; + + if (delta != 0) { + int uid = mUidStats.keyAt(i); + long[] stats = mPowerStats.uidStats.get(uid); + if (stats == null) { + stats = new long[mLayout.getUidStatsArrayLength()]; + mPowerStats.uidStats.put(uid, stats); + } + + mLayout.setUidScanTime(stats, delta); + totalScanTime += delta; + } + } + + mLayout.setDeviceScanTime(mDeviceStats, totalScanTime); + } + + private void collectEnergyConsumers() { + int voltageMv = mVoltageSupplier.getAsInt(); + if (voltageMv <= 0) { + Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMv + + " mV) when querying energy consumers"); + return; + } + + int averageVoltage = mLastVoltageMv != 0 ? (mLastVoltageMv + voltageMv) / 2 : voltageMv; + mLastVoltageMv = voltageMv; + + long[] energyUws = mConsumedEnergyRetriever.getConsumedEnergyUws(mEnergyConsumerIds); + if (energyUws == null) { + return; + } + + for (int i = energyUws.length - 1; i >= 0; i--) { + long energyDelta = mLastConsumedEnergyUws[i] != ENERGY_UNSPECIFIED + ? energyUws[i] - mLastConsumedEnergyUws[i] : 0; + if (energyDelta < 0) { + // Likely, restart of powerstats HAL + energyDelta = 0; + } + mLayout.setConsumedEnergy(mPowerStats.stats, i, uJtoUc(energyDelta, averageVoltage)); + mLastConsumedEnergyUws[i] = energyUws[i]; + } + } + + @Override + protected void onUidRemoved(int uid) { + super.onUidRemoved(uid); + mUidStats.remove(uid); + } +} diff --git a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsLayout.java new file mode 100644 index 000000000000..9358b5ef20a8 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsLayout.java @@ -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.server.power.stats; + +import android.annotation.NonNull; +import android.os.PersistableBundle; + +import com.android.internal.os.PowerStats; + +public class BluetoothPowerStatsLayout extends PowerStatsLayout { + private static final String EXTRA_DEVICE_RX_TIME_POSITION = "dt-rx"; + private static final String EXTRA_DEVICE_TX_TIME_POSITION = "dt-tx"; + private static final String EXTRA_DEVICE_IDLE_TIME_POSITION = "dt-idle"; + private static final String EXTRA_DEVICE_SCAN_TIME_POSITION = "dt-scan"; + private static final String EXTRA_UID_RX_BYTES_POSITION = "ub-rx"; + private static final String EXTRA_UID_TX_BYTES_POSITION = "ub-tx"; + private static final String EXTRA_UID_SCAN_TIME_POSITION = "ut-scan"; + + private int mDeviceRxTimePosition; + private int mDeviceTxTimePosition; + private int mDeviceIdleTimePosition; + private int mDeviceScanTimePosition; + private int mUidRxBytesPosition; + private int mUidTxBytesPosition; + private int mUidScanTimePosition; + + BluetoothPowerStatsLayout() { + } + + BluetoothPowerStatsLayout(@NonNull PowerStats.Descriptor descriptor) { + super(descriptor); + } + + void addDeviceBluetoothControllerActivity() { + mDeviceRxTimePosition = addDeviceSection(1, "rx"); + mDeviceTxTimePosition = addDeviceSection(1, "tx"); + mDeviceIdleTimePosition = addDeviceSection(1, "idle"); + mDeviceScanTimePosition = addDeviceSection(1, "scan", FLAG_OPTIONAL); + } + + void addUidTrafficStats() { + mUidRxBytesPosition = addUidSection(1, "rx-B"); + mUidTxBytesPosition = addUidSection(1, "tx-B"); + mUidScanTimePosition = addUidSection(1, "scan", FLAG_OPTIONAL); + } + + public void setDeviceRxTime(long[] stats, long durationMillis) { + stats[mDeviceRxTimePosition] = durationMillis; + } + + public long getDeviceRxTime(long[] stats) { + return stats[mDeviceRxTimePosition]; + } + + public void setDeviceTxTime(long[] stats, long durationMillis) { + stats[mDeviceTxTimePosition] = durationMillis; + } + + public long getDeviceTxTime(long[] stats) { + return stats[mDeviceTxTimePosition]; + } + + public void setDeviceIdleTime(long[] stats, long durationMillis) { + stats[mDeviceIdleTimePosition] = durationMillis; + } + + public long getDeviceIdleTime(long[] stats) { + return stats[mDeviceIdleTimePosition]; + } + + public void setDeviceScanTime(long[] stats, long durationMillis) { + stats[mDeviceScanTimePosition] = durationMillis; + } + + public long getDeviceScanTime(long[] stats) { + return stats[mDeviceScanTimePosition]; + } + + public void setUidRxBytes(long[] stats, long count) { + stats[mUidRxBytesPosition] = count; + } + + public long getUidRxBytes(long[] stats) { + return stats[mUidRxBytesPosition]; + } + + public void setUidTxBytes(long[] stats, long count) { + stats[mUidTxBytesPosition] = count; + } + + public long getUidTxBytes(long[] stats) { + return stats[mUidTxBytesPosition]; + } + + public void setUidScanTime(long[] stats, long count) { + stats[mUidScanTimePosition] = count; + } + + public long getUidScanTime(long[] stats) { + return stats[mUidScanTimePosition]; + } + + /** + * Copies the elements of the stats array layout into <code>extras</code> + */ + public void toExtras(PersistableBundle extras) { + super.toExtras(extras); + extras.putInt(EXTRA_DEVICE_RX_TIME_POSITION, mDeviceRxTimePosition); + extras.putInt(EXTRA_DEVICE_TX_TIME_POSITION, mDeviceTxTimePosition); + extras.putInt(EXTRA_DEVICE_IDLE_TIME_POSITION, mDeviceIdleTimePosition); + extras.putInt(EXTRA_DEVICE_SCAN_TIME_POSITION, mDeviceScanTimePosition); + extras.putInt(EXTRA_UID_RX_BYTES_POSITION, mUidRxBytesPosition); + extras.putInt(EXTRA_UID_TX_BYTES_POSITION, mUidTxBytesPosition); + extras.putInt(EXTRA_UID_SCAN_TIME_POSITION, mUidScanTimePosition); + } + + /** + * Retrieves elements of the stats array layout from <code>extras</code> + */ + public void fromExtras(PersistableBundle extras) { + super.fromExtras(extras); + mDeviceRxTimePosition = extras.getInt(EXTRA_DEVICE_RX_TIME_POSITION); + mDeviceTxTimePosition = extras.getInt(EXTRA_DEVICE_TX_TIME_POSITION); + mDeviceIdleTimePosition = extras.getInt(EXTRA_DEVICE_IDLE_TIME_POSITION); + mDeviceScanTimePosition = extras.getInt(EXTRA_DEVICE_SCAN_TIME_POSITION); + mUidRxBytesPosition = extras.getInt(EXTRA_UID_RX_BYTES_POSITION); + mUidTxBytesPosition = extras.getInt(EXTRA_UID_TX_BYTES_POSITION); + mUidScanTimePosition = extras.getInt(EXTRA_UID_SCAN_TIME_POSITION); + } +} diff --git a/services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java new file mode 100644 index 000000000000..4d6db9703ce7 --- /dev/null +++ b/services/core/java/com/android/server/power/stats/BluetoothPowerStatsProcessor.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.power.stats; + +import com.android.internal.os.PowerProfile; +import com.android.internal.os.PowerStats; + +import java.util.ArrayList; +import java.util.List; + +public class BluetoothPowerStatsProcessor extends PowerStatsProcessor { + private static final String TAG = "BluetoothPowerStatsProcessor"; + + private final UsageBasedPowerEstimator mRxPowerEstimator; + private final UsageBasedPowerEstimator mTxPowerEstimator; + private final UsageBasedPowerEstimator mIdlePowerEstimator; + + private PowerStats.Descriptor mLastUsedDescriptor; + private BluetoothPowerStatsLayout mStatsLayout; + // Sequence of steps for power estimation and intermediate results. + private PowerEstimationPlan mPlan; + + private long[] mTmpDeviceStatsArray; + private long[] mTmpUidStatsArray; + + public BluetoothPowerStatsProcessor(PowerProfile powerProfile) { + mRxPowerEstimator = new UsageBasedPowerEstimator( + powerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX)); + mTxPowerEstimator = new UsageBasedPowerEstimator( + powerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX)); + mIdlePowerEstimator = new UsageBasedPowerEstimator( + powerProfile.getAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE)); + } + + private static class Intermediates { + /** + * Number of received bytes + */ + public long rxBytes; + /** + * Duration of receiving + */ + public long rxTime; + /** + * Estimated power for the RX state. + */ + public double rxPower; + /** + * Number of transmitted bytes + */ + public long txBytes; + /** + * Duration of transmitting + */ + public long txTime; + /** + * Estimated power for the TX state. + */ + public double txPower; + /** + * Estimated power for IDLE, SCAN states. + */ + public double idlePower; + /** + * Total scan time. + */ + public long scanTime; + /** + * Measured consumed energy from power monitoring hardware (micro-coulombs) + */ + public long consumedEnergy; + } + + @Override + void finish(PowerComponentAggregatedPowerStats stats) { + if (stats.getPowerStatsDescriptor() == null) { + return; + } + + unpackPowerStatsDescriptor(stats.getPowerStatsDescriptor()); + + if (mPlan == null) { + mPlan = new PowerEstimationPlan(stats.getConfig()); + } + + for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) { + DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i); + Intermediates intermediates = new Intermediates(); + estimation.intermediates = intermediates; + computeDevicePowerEstimates(stats, estimation.stateValues, intermediates); + } + + double ratio = 1.0; + if (mStatsLayout.getEnergyConsumerCount() != 0) { + ratio = computeEstimateAdjustmentRatioUsingConsumedEnergy(); + if (ratio != 1) { + for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) { + DeviceStateEstimation estimation = mPlan.deviceStateEstimations.get(i); + adjustDevicePowerEstimates(stats, estimation.stateValues, + (Intermediates) estimation.intermediates, ratio); + } + } + } + + combineDeviceStateEstimates(); + + ArrayList<Integer> uids = new ArrayList<>(); + stats.collectUids(uids); + if (!uids.isEmpty()) { + for (int uid : uids) { + for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { + computeUidActivityTotals(stats, uid, mPlan.uidStateEstimates.get(i)); + } + } + + for (int uid : uids) { + for (int i = 0; i < mPlan.uidStateEstimates.size(); i++) { + computeUidPowerEstimates(stats, uid, mPlan.uidStateEstimates.get(i)); + } + } + } + mPlan.resetIntermediates(); + } + + private void unpackPowerStatsDescriptor(PowerStats.Descriptor descriptor) { + if (descriptor.equals(mLastUsedDescriptor)) { + return; + } + + mLastUsedDescriptor = descriptor; + mStatsLayout = new BluetoothPowerStatsLayout(descriptor); + mTmpDeviceStatsArray = new long[descriptor.statsArrayLength]; + mTmpUidStatsArray = new long[descriptor.uidStatsArrayLength]; + } + + /** + * Compute power estimates using the power profile. + */ + private void computeDevicePowerEstimates(PowerComponentAggregatedPowerStats stats, + int[] deviceStates, Intermediates intermediates) { + if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStates)) { + return; + } + + for (int i = mStatsLayout.getEnergyConsumerCount() - 1; i >= 0; i--) { + intermediates.consumedEnergy += mStatsLayout.getConsumedEnergy(mTmpDeviceStatsArray, i); + } + + intermediates.rxTime = mStatsLayout.getDeviceRxTime(mTmpDeviceStatsArray); + intermediates.txTime = mStatsLayout.getDeviceTxTime(mTmpDeviceStatsArray); + intermediates.scanTime = mStatsLayout.getDeviceScanTime(mTmpDeviceStatsArray); + long idleTime = mStatsLayout.getDeviceIdleTime(mTmpDeviceStatsArray); + + intermediates.rxPower = mRxPowerEstimator.calculatePower(intermediates.rxTime); + intermediates.txPower = mTxPowerEstimator.calculatePower(intermediates.txTime); + intermediates.idlePower = mIdlePowerEstimator.calculatePower(idleTime); + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, + intermediates.rxPower + intermediates.txPower + intermediates.idlePower); + stats.setDeviceStats(deviceStates, mTmpDeviceStatsArray); + } + + /** + * Compute an adjustment ratio using the total power estimated using the power profile + * and the total power measured by hardware. + */ + private double computeEstimateAdjustmentRatioUsingConsumedEnergy() { + long totalConsumedEnergy = 0; + double totalPower = 0; + + for (int i = mPlan.deviceStateEstimations.size() - 1; i >= 0; i--) { + Intermediates intermediates = + (Intermediates) mPlan.deviceStateEstimations.get(i).intermediates; + totalPower += intermediates.rxPower + intermediates.txPower + intermediates.idlePower; + totalConsumedEnergy += intermediates.consumedEnergy; + } + + if (totalPower == 0) { + return 1; + } + + return uCtoMah(totalConsumedEnergy) / totalPower; + } + + /** + * Uniformly apply the same adjustment to all power estimates in order to ensure that the total + * estimated power matches the measured consumed power. We are not claiming that all + * averages captured in the power profile have to be off by the same percentage in reality. + */ + private void adjustDevicePowerEstimates(PowerComponentAggregatedPowerStats stats, + int[] deviceStates, Intermediates intermediates, double ratio) { + double adjutedPower; + intermediates.rxPower *= ratio; + intermediates.txPower *= ratio; + intermediates.idlePower *= ratio; + adjutedPower = intermediates.rxPower + intermediates.txPower + intermediates.idlePower; + + if (!stats.getDeviceStats(mTmpDeviceStatsArray, deviceStates)) { + return; + } + + mStatsLayout.setDevicePowerEstimate(mTmpDeviceStatsArray, adjutedPower); + stats.setDeviceStats(deviceStates, mTmpDeviceStatsArray); + } + + /** + * Combine power estimates before distributing them proportionally to UIDs. + */ + private void combineDeviceStateEstimates() { + for (int i = mPlan.combinedDeviceStateEstimations.size() - 1; i >= 0; i--) { + CombinedDeviceStateEstimate cdse = mPlan.combinedDeviceStateEstimations.get(i); + Intermediates cdseIntermediates = new Intermediates(); + cdse.intermediates = cdseIntermediates; + List<DeviceStateEstimation> deviceStateEstimations = cdse.deviceStateEstimations; + for (int j = deviceStateEstimations.size() - 1; j >= 0; j--) { + DeviceStateEstimation dse = deviceStateEstimations.get(j); + Intermediates intermediates = (Intermediates) dse.intermediates; + cdseIntermediates.rxTime += intermediates.rxTime; + cdseIntermediates.rxBytes += intermediates.rxBytes; + cdseIntermediates.rxPower += intermediates.rxPower; + cdseIntermediates.txTime += intermediates.txTime; + cdseIntermediates.txBytes += intermediates.txBytes; + cdseIntermediates.txPower += intermediates.txPower; + cdseIntermediates.idlePower += intermediates.idlePower; + cdseIntermediates.scanTime += intermediates.scanTime; + cdseIntermediates.consumedEnergy += intermediates.consumedEnergy; + } + } + } + + private void computeUidActivityTotals(PowerComponentAggregatedPowerStats stats, int uid, + UidStateEstimate uidStateEstimate) { + Intermediates intermediates = + (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates; + for (UidStateProportionalEstimate proportionalEstimate : + uidStateEstimate.proportionalEstimates) { + if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { + continue; + } + + intermediates.rxBytes += mStatsLayout.getUidRxBytes(mTmpUidStatsArray); + intermediates.txBytes += mStatsLayout.getUidTxBytes(mTmpUidStatsArray); + } + } + + private void computeUidPowerEstimates(PowerComponentAggregatedPowerStats stats, int uid, + UidStateEstimate uidStateEstimate) { + Intermediates intermediates = + (Intermediates) uidStateEstimate.combinedDeviceStateEstimate.intermediates; + + // Scan is more expensive than data transfer, so in the presence of large + // of scanning duration, blame apps according to the time they spent scanning. + // This may disproportionately blame apps that do a lot of scanning, which is + // the tread-off we are making in the absence of more detailed metrics. + boolean normalizeRxByScanTime = intermediates.scanTime > intermediates.rxTime; + boolean normalizeTxByScanTime = intermediates.scanTime > intermediates.txTime; + + for (UidStateProportionalEstimate proportionalEstimate : + uidStateEstimate.proportionalEstimates) { + if (!stats.getUidStats(mTmpUidStatsArray, uid, proportionalEstimate.stateValues)) { + continue; + } + + double power = 0; + if (normalizeRxByScanTime) { + if (intermediates.scanTime != 0) { + power += intermediates.rxPower * mStatsLayout.getUidScanTime(mTmpUidStatsArray) + / intermediates.scanTime; + } + } else { + if (intermediates.rxBytes != 0) { + power += intermediates.rxPower * mStatsLayout.getUidRxBytes(mTmpUidStatsArray) + / intermediates.rxBytes; + } + } + if (normalizeTxByScanTime) { + if (intermediates.scanTime != 0) { + power += intermediates.txPower * mStatsLayout.getUidScanTime(mTmpUidStatsArray) + / intermediates.scanTime; + } + } else { + if (intermediates.txBytes != 0) { + power += intermediates.txPower * mStatsLayout.getUidTxBytes(mTmpUidStatsArray) + / intermediates.txBytes; + } + } + mStatsLayout.setUidPowerEstimate(mTmpUidStatsArray, power); + stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); + } + } +} diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java index f53a1b0682c0..b5ef67b44e75 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java @@ -59,6 +59,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { KernelCpuStatsReader getKernelCpuStatsReader(); ConsumedEnergyRetriever getConsumedEnergyRetriever(); IntSupplier getVoltageSupplier(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); default int getDefaultCpuPowerBrackets() { return DEFAULT_CPU_POWER_BRACKETS; @@ -94,9 +95,11 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private int mLastVoltageMv; private long[] mLastConsumedEnergyUws; - public CpuPowerStatsCollector(Injector injector, long throttlePeriodMs) { - super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(), - injector.getClock()); + CpuPowerStatsCollector(Injector injector) { + super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_CPU)), + injector.getUidResolver(), injector.getClock()); mInjector = injector; } diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java index 1bcb2c4bc5fa..2a02bd0f9e6a 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsLayout.java @@ -44,7 +44,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout { * Declare that the stats array has a section capturing CPU time per scaling step */ public void addDeviceSectionCpuTimeByScalingStep(int scalingStepCount) { - mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount); + mDeviceCpuTimeByScalingStepPosition = addDeviceSection(scalingStepCount, "steps"); mDeviceCpuTimeByScalingStepCount = scalingStepCount; } @@ -72,7 +72,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout { * Declare that the stats array has a section capturing CPU time in each cluster */ public void addDeviceSectionCpuTimeByCluster(int clusterCount) { - mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount); + mDeviceCpuTimeByClusterPosition = addDeviceSection(clusterCount, "clusters"); mDeviceCpuTimeByClusterCount = clusterCount; } @@ -102,7 +102,7 @@ public class CpuPowerStatsLayout extends PowerStatsLayout { public void addUidSectionCpuTimeByPowerBracket(int[] scalingStepToPowerBracketMap) { mScalingStepToPowerBracketMap = scalingStepToPowerBracketMap; updatePowerBracketCount(); - mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount); + mUidPowerBracketsPosition = addUidSection(mUidPowerBracketCount, "time"); } private void updatePowerBracketCount() { diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java index c34b8a8dc992..57b7259f9b56 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsProcessor.java @@ -16,7 +16,6 @@ package com.android.server.power.stats; -import android.os.BatteryStats; import android.util.ArraySet; import android.util.Log; @@ -487,64 +486,4 @@ public class CpuPowerStatsProcessor extends PowerStatsProcessor { stats.setUidStats(uid, proportionalEstimate.stateValues, mTmpUidStatsArray); } } - - @Override - public String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - StringBuilder sb = new StringBuilder(); - int cpuScalingStepCount = mStatsLayout.getCpuScalingStepCount(); - sb.append("steps: ["); - for (int step = 0; step < cpuScalingStepCount; step++) { - if (step != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getTimeByScalingStep(stats, step)); - } - int clusterCount = mStatsLayout.getCpuClusterCount(); - sb.append("] clusters: ["); - for (int cluster = 0; cluster < clusterCount; cluster++) { - if (cluster != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getTimeByCluster(stats, cluster)); - } - sb.append("] uptime: ").append(mStatsLayout.getUsageDuration(stats)); - int energyConsumerCount = mStatsLayout.getEnergyConsumerCount(); - if (energyConsumerCount > 0) { - sb.append(" energy: ["); - for (int i = 0; i < energyConsumerCount; i++) { - if (i != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getConsumedEnergy(stats, i)); - } - sb.append("]"); - } - sb.append(" power: ").append( - BatteryStats.formatCharge(mStatsLayout.getDevicePowerEstimate(stats))); - return sb.toString(); - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - // Unsupported for this power component - return null; - } - - @Override - public String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - StringBuilder sb = new StringBuilder(); - sb.append("["); - int powerBracketCount = mStatsLayout.getCpuPowerBracketCount(); - for (int bracket = 0; bracket < powerBracketCount; bracket++) { - if (bracket != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getUidTimeByPowerBracket(stats, bracket)); - } - sb.append("] power: ").append( - BatteryStats.formatCharge(mStatsLayout.getUidPowerEstimate(stats))); - return sb.toString(); - } } diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java index 7bc681752802..a96e01bdeadf 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java @@ -73,6 +73,7 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { Handler getHandler(); Clock getClock(); PowerStatsUidResolver getUidResolver(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); PackageManager getPackageManager(); ConsumedEnergyRetriever getConsumedEnergyRetriever(); IntSupplier getVoltageSupplier(); @@ -104,8 +105,11 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { private long mLastCallDuration; private long mLastScanDuration; - public MobileRadioPowerStatsCollector(Injector injector, long throttlePeriodMs) { - super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(), + MobileRadioPowerStatsCollector(Injector injector) { + super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)), + injector.getUidResolver(), injector.getClock()); mInjector = injector; } diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java index 81d7c2fa2880..07d78f8ce4d0 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsLayout.java @@ -64,29 +64,30 @@ class MobileRadioPowerStatsLayout extends PowerStatsLayout { } void addDeviceMobileActivity() { - mDeviceSleepTimePosition = addDeviceSection(1); - mDeviceIdleTimePosition = addDeviceSection(1); - mDeviceScanTimePosition = addDeviceSection(1); - mDeviceCallTimePosition = addDeviceSection(1); + mDeviceSleepTimePosition = addDeviceSection(1, "sleep"); + mDeviceIdleTimePosition = addDeviceSection(1, "idle"); + mDeviceScanTimePosition = addDeviceSection(1, "scan"); + mDeviceCallTimePosition = addDeviceSection(1, "call", FLAG_OPTIONAL); } void addStateStats() { - mStateRxTimePosition = addStateSection(1); + mStateRxTimePosition = addStateSection(1, "rx"); mStateTxTimesCount = ModemActivityInfo.getNumTxPowerLevels(); - mStateTxTimesPosition = addStateSection(mStateTxTimesCount); + mStateTxTimesPosition = addStateSection(mStateTxTimesCount, "tx"); } void addUidNetworkStats() { - mUidRxBytesPosition = addUidSection(1); - mUidTxBytesPosition = addUidSection(1); - mUidRxPacketsPosition = addUidSection(1); - mUidTxPacketsPosition = addUidSection(1); + mUidRxPacketsPosition = addUidSection(1, "rx-pkts"); + mUidRxBytesPosition = addUidSection(1, "rx-B"); + mUidTxPacketsPosition = addUidSection(1, "tx-pkts"); + mUidTxBytesPosition = addUidSection(1, "tx-B"); } @Override public void addDeviceSectionPowerEstimate() { super.addDeviceSectionPowerEstimate(); - mDeviceCallPowerPosition = addDeviceSection(1); + // Printed as part of the PhoneCallPowerStatsProcessor + mDeviceCallPowerPosition = addDeviceSection(1, "call-power", FLAG_HIDDEN); } public void setDeviceSleepTime(long[] stats, long durationMillis) { diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java index c97c64bafcba..eebed2f21946 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsProcessor.java @@ -398,37 +398,4 @@ public class MobileRadioPowerStatsProcessor extends PowerStatsProcessor { } } } - - @Override - String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - return "idle: " + mStatsLayout.getDeviceIdleTime(stats) - + " sleep: " + mStatsLayout.getDeviceSleepTime(stats) - + " scan: " + mStatsLayout.getDeviceScanTime(stats) - + " power: " + mStatsLayout.getDevicePowerEstimate(stats); - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - StringBuilder sb = new StringBuilder(); - sb.append(descriptor.getStateLabel(key)); - sb.append(" rx: ").append(mStatsLayout.getStateRxTime(stats)); - sb.append(" tx: "); - for (int txLevel = 0; txLevel < ModemActivityInfo.getNumTxPowerLevels(); txLevel++) { - if (txLevel != 0) { - sb.append(", "); - } - sb.append(mStatsLayout.getStateTxTime(stats, txLevel)); - } - return sb.toString(); - } - - @Override - String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - return "rx: " + mStatsLayout.getUidRxPackets(stats) - + " tx: " + mStatsLayout.getUidTxPackets(stats) - + " power: " + mStatsLayout.getUidPowerEstimate(stats); - } } diff --git a/services/core/java/com/android/server/power/stats/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java index 6c4a2b6e6359..a8222811c341 100644 --- a/services/core/java/com/android/server/power/stats/MultiStateStats.java +++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java @@ -28,10 +28,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.io.PrintWriter; import java.util.Arrays; import java.util.function.Consumer; -import java.util.function.Function; /** * Maintains multidimensional multi-state stats. States could be something like on-battery (0,1), @@ -287,6 +285,14 @@ public class MultiStateStats { mCounter = new LongArrayMultiStateCounter(factory.mSerialStateCount, dimensionCount); } + public int getDimensionCount() { + return mFactory.mDimensionCount; + } + + public States[] getStates() { + return mFactory.mStates; + } + /** * Copies time-in-state and timestamps from the supplied prototype. Does not * copy accumulated counts. @@ -343,11 +349,6 @@ public class MultiStateStats { mTracking = false; } - @Override - public String toString() { - return mCounter.toString(); - } - /** * Stores contents in an XML doc. */ @@ -451,10 +452,9 @@ public class MultiStateStats { return true; } - /** - * Prints the accumulated stats, one line of every combination of states that has data. - */ - public void dump(PrintWriter pw, Function<long[], String> statsFormatter) { + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); long[] values = new long[mCounter.getArrayLength()]; States.forEachTrackedStateCombination(mFactory.mStates, states -> { mCounter.getCounts(values, mFactory.getSerialState(states)); @@ -469,18 +469,24 @@ public class MultiStateStats { return; } - StringBuilder sb = new StringBuilder(); + if (!sb.isEmpty()) { + sb.append("\n"); + } + + sb.append("("); + boolean first = true; for (int i = 0; i < states.length; i++) { if (mFactory.mStates[i].mTracked) { - if (sb.length() != 0) { + if (!first) { sb.append(" "); } + first = false; sb.append(mFactory.mStates[i].mLabels[states[i]]); } } - sb.append(" "); - sb.append(statsFormatter.apply(values)); - pw.println(sb); + sb.append(") "); + sb.append(Arrays.toString(values)); }); + return sb.toString(); } } diff --git a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java index 62b653f61373..5c545fd073b2 100644 --- a/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/PhoneCallPowerStatsProcessor.java @@ -76,21 +76,4 @@ public class PhoneCallPowerStatsProcessor extends PowerStatsProcessor { stats.setDeviceStats(states, mTmpDeviceStats); }); } - - @Override - String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - return "power: " + mStatsLayout.getDevicePowerEstimate(stats); - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - // Unsupported for this power component - return null; - } - - @Override - String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - // Unsupported for this power component - return null; - } } diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 6d58307dbefa..052873312d5c 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -436,36 +436,76 @@ class PowerComponentAggregatedPowerStats { void dumpDevice(IndentingPrintWriter ipw) { if (mDeviceStats != null) { - ipw.println(mPowerStatsDescriptor.name); - ipw.increaseIndent(); - mDeviceStats.dump(ipw, stats -> - mConfig.getProcessor().deviceStatsToString(mPowerStatsDescriptor, stats)); - ipw.decreaseIndent(); + dumpMultiStateStats(ipw, mDeviceStats, mPowerStatsDescriptor.name, null, + mPowerStatsDescriptor.getDeviceStatsFormatter()); } if (mStateStats.size() != 0) { ipw.increaseIndent(); - ipw.println(mPowerStatsDescriptor.name + " states"); - ipw.increaseIndent(); + String header = mPowerStatsDescriptor.name + " states"; + PowerStats.PowerStatsFormatter formatter = + mPowerStatsDescriptor.getStateStatsFormatter(); for (int i = 0; i < mStateStats.size(); i++) { int key = mStateStats.keyAt(i); + String stateLabel = mPowerStatsDescriptor.getStateLabel(key); MultiStateStats stateStats = mStateStats.valueAt(i); - stateStats.dump(ipw, stats -> - mConfig.getProcessor().stateStatsToString(mPowerStatsDescriptor, key, - stats)); + dumpMultiStateStats(ipw, stateStats, header, stateLabel, formatter); } ipw.decreaseIndent(); - ipw.decreaseIndent(); } } void dumpUid(IndentingPrintWriter ipw, int uid) { UidStats uidStats = mUidStats.get(uid); if (uidStats != null && uidStats.stats != null) { - ipw.println(mPowerStatsDescriptor.name); - ipw.increaseIndent(); - uidStats.stats.dump(ipw, stats -> - mConfig.getProcessor().uidStatsToString(mPowerStatsDescriptor, stats)); + dumpMultiStateStats(ipw, uidStats.stats, mPowerStatsDescriptor.name, null, + mPowerStatsDescriptor.getUidStatsFormatter()); + } + } + + private void dumpMultiStateStats(IndentingPrintWriter ipw, MultiStateStats stats, + String header, String additionalLabel, + PowerStats.PowerStatsFormatter statsFormatter) { + boolean[] firstLine = new boolean[]{true}; + long[] values = new long[stats.getDimensionCount()]; + MultiStateStats.States[] stateInfo = stats.getStates(); + MultiStateStats.States.forEachTrackedStateCombination(stateInfo, states -> { + stats.getStats(values, states); + boolean nonZero = false; + for (long value : values) { + if (value != 0) { + nonZero = true; + break; + } + } + if (!nonZero) { + return; + } + + if (firstLine[0]) { + ipw.println(header); + ipw.increaseIndent(); + } + firstLine[0] = false; + StringBuilder sb = new StringBuilder(); + sb.append("("); + boolean first = true; + for (int i = 0; i < states.length; i++) { + if (stateInfo[i].isTracked()) { + if (!first) { + sb.append(" "); + } + first = false; + sb.append(stateInfo[i].getLabels()[states[i]]); + } + } + if (additionalLabel != null) { + sb.append(" ").append(additionalLabel); + } + sb.append(") ").append(statsFormatter.format(values)); + ipw.println(sb); + }); + if (!firstLine[0]) { ipw.decreaseIndent(); } } diff --git a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java index aa96409e85e9..58efd94bb82c 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsLayout.java @@ -33,13 +33,20 @@ public class PowerStatsLayout { private static final String EXTRA_DEVICE_ENERGY_CONSUMERS_COUNT = "dec"; private static final String EXTRA_UID_POWER_POSITION = "up"; - protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; protected static final int UNSUPPORTED = -1; + protected static final double MILLI_TO_NANO_MULTIPLIER = 1000000.0; + protected static final int FLAG_OPTIONAL = 1; + protected static final int FLAG_HIDDEN = 2; + protected static final int FLAG_FORMAT_AS_POWER = 4; private int mDeviceStatsArrayLength; private int mStateStatsArrayLength; private int mUidStatsArrayLength; + private StringBuilder mDeviceFormat = new StringBuilder(); + private StringBuilder mStateFormat = new StringBuilder(); + private StringBuilder mUidFormat = new StringBuilder(); + protected int mDeviceDurationPosition = UNSUPPORTED; private int mDeviceEnergyConsumerPosition; private int mDeviceEnergyConsumerCount; @@ -65,29 +72,71 @@ public class PowerStatsLayout { return mUidStatsArrayLength; } - protected int addDeviceSection(int length) { + /** + * @param label should not contain either spaces or colons + */ + private void appendFormat(StringBuilder sb, int position, int length, String label, + int flags) { + if ((flags & FLAG_HIDDEN) != 0) { + return; + } + + if (!sb.isEmpty()) { + sb.append(' '); + } + + sb.append(label).append(':'); + sb.append(position); + if (length != 1) { + sb.append('[').append(length).append(']'); + } + if ((flags & FLAG_FORMAT_AS_POWER) != 0) { + sb.append('p'); + } + if ((flags & FLAG_OPTIONAL) != 0) { + sb.append('?'); + } + } + + protected int addDeviceSection(int length, String label, int flags) { int position = mDeviceStatsArrayLength; mDeviceStatsArrayLength += length; + appendFormat(mDeviceFormat, position, length, label, flags); return position; } - protected int addStateSection(int length) { + protected int addDeviceSection(int length, String label) { + return addDeviceSection(length, label, 0); + } + + protected int addStateSection(int length, String label, int flags) { int position = mStateStatsArrayLength; mStateStatsArrayLength += length; + appendFormat(mStateFormat, position, length, label, flags); return position; } - protected int addUidSection(int length) { + protected int addStateSection(int length, String label) { + return addStateSection(length, label, 0); + } + + + protected int addUidSection(int length, String label, int flags) { int position = mUidStatsArrayLength; mUidStatsArrayLength += length; + appendFormat(mUidFormat, position, length, label, flags); return position; } + protected int addUidSection(int length, String label) { + return addUidSection(length, label, 0); + } + /** * Declare that the stats array has a section capturing usage duration */ public void addDeviceSectionUsageDuration() { - mDeviceDurationPosition = addDeviceSection(1); + mDeviceDurationPosition = addDeviceSection(1, "usage", FLAG_OPTIONAL); } /** @@ -109,7 +158,7 @@ public class PowerStatsLayout { * PowerStatsService. */ public void addDeviceSectionEnergyConsumers(int energyConsumerCount) { - mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount); + mDeviceEnergyConsumerPosition = addDeviceSection(energyConsumerCount, "energy"); mDeviceEnergyConsumerCount = energyConsumerCount; } @@ -137,7 +186,8 @@ public class PowerStatsLayout { * Declare that the stats array has a section capturing a power estimate */ public void addDeviceSectionPowerEstimate() { - mDevicePowerEstimatePosition = addDeviceSection(1); + mDevicePowerEstimatePosition = addDeviceSection(1, "power", + FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL); } /** @@ -159,7 +209,7 @@ public class PowerStatsLayout { * Declare that the UID stats array has a section capturing a power estimate */ public void addUidSectionPowerEstimate() { - mUidPowerEstimatePosition = addUidSection(1); + mUidPowerEstimatePosition = addUidSection(1, "power", FLAG_FORMAT_AS_POWER | FLAG_OPTIONAL); } /** @@ -195,6 +245,9 @@ public class PowerStatsLayout { mDeviceEnergyConsumerCount); extras.putInt(EXTRA_DEVICE_POWER_POSITION, mDevicePowerEstimatePosition); extras.putInt(EXTRA_UID_POWER_POSITION, mUidPowerEstimatePosition); + extras.putString(PowerStats.Descriptor.EXTRA_DEVICE_STATS_FORMAT, mDeviceFormat.toString()); + extras.putString(PowerStats.Descriptor.EXTRA_STATE_STATS_FORMAT, mStateFormat.toString()); + extras.putString(PowerStats.Descriptor.EXTRA_UID_STATS_FORMAT, mUidFormat.toString()); } /** diff --git a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java index 0d5c5422b45c..2fd0b9a9b001 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsProcessor.java @@ -19,8 +19,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; -import com.android.internal.os.PowerStats; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -47,12 +45,6 @@ abstract class PowerStatsProcessor { abstract void finish(PowerComponentAggregatedPowerStats stats); - abstract String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats); - - abstract String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats); - - abstract String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats); - protected static class PowerEstimationPlan { private final AggregatedPowerStatsConfig.PowerComponent mConfig; public List<DeviceStateEstimation> deviceStateEstimations = new ArrayList<>(); diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java index 632105352ad2..bd04199fc227 100644 --- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java @@ -56,6 +56,7 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { Handler getHandler(); Clock getClock(); PowerStatsUidResolver getUidResolver(); + long getPowerStatsCollectionThrottlePeriod(String powerComponentName); PackageManager getPackageManager(); ConsumedEnergyRetriever getConsumedEnergyRetriever(); IntSupplier getVoltageSupplier(); @@ -92,9 +93,11 @@ public class WifiPowerStatsCollector extends PowerStatsCollector { private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>(); private long mLastWifiActiveDuration; - public WifiPowerStatsCollector(Injector injector, long throttlePeriodMs) { - super(injector.getHandler(), throttlePeriodMs, injector.getUidResolver(), - injector.getClock()); + WifiPowerStatsCollector(Injector injector) { + super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_WIFI)), + injector.getUidResolver(), injector.getClock()); mInjector = injector; } diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java index 0fa6ec65c4bc..e2e822690c55 100644 --- a/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java +++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsLayout.java @@ -65,28 +65,28 @@ public class WifiPowerStatsLayout extends PowerStatsLayout { mPowerReportingSupported = powerReportingSupported; if (mPowerReportingSupported) { mDeviceActiveTimePosition = UNSPECIFIED; - mDeviceRxTimePosition = addDeviceSection(1); - mDeviceTxTimePosition = addDeviceSection(1); - mDeviceIdleTimePosition = addDeviceSection(1); - mDeviceScanTimePosition = addDeviceSection(1); + mDeviceRxTimePosition = addDeviceSection(1, "rx"); + mDeviceTxTimePosition = addDeviceSection(1, "tx"); + mDeviceIdleTimePosition = addDeviceSection(1, "idle"); + mDeviceScanTimePosition = addDeviceSection(1, "scan"); } else { - mDeviceActiveTimePosition = addDeviceSection(1); + mDeviceActiveTimePosition = addDeviceSection(1, "rx-tx"); mDeviceRxTimePosition = UNSPECIFIED; mDeviceTxTimePosition = UNSPECIFIED; mDeviceIdleTimePosition = UNSPECIFIED; mDeviceScanTimePosition = UNSPECIFIED; } - mDeviceBasicScanTimePosition = addDeviceSection(1); - mDeviceBatchedScanTimePosition = addDeviceSection(1); + mDeviceBasicScanTimePosition = addDeviceSection(1, "basic-scan", FLAG_OPTIONAL); + mDeviceBatchedScanTimePosition = addDeviceSection(1, "batched-scan", FLAG_OPTIONAL); } void addUidNetworkStats() { - mUidRxBytesPosition = addUidSection(1); - mUidTxBytesPosition = addUidSection(1); - mUidRxPacketsPosition = addUidSection(1); - mUidTxPacketsPosition = addUidSection(1); - mUidScanTimePosition = addUidSection(1); - mUidBatchScanTimePosition = addUidSection(1); + mUidRxPacketsPosition = addUidSection(1, "rx-pkts"); + mUidRxBytesPosition = addUidSection(1, "rx-B"); + mUidTxPacketsPosition = addUidSection(1, "tx-pkts"); + mUidTxBytesPosition = addUidSection(1, "tx-B"); + mUidScanTimePosition = addUidSection(1, "scan", FLAG_OPTIONAL); + mUidBatchScanTimePosition = addUidSection(1, "batched-scan", FLAG_OPTIONAL); } public boolean isPowerReportingSupported() { diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java index 5e9cc4092029..a4a2e183f86b 100644 --- a/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsProcessor.java @@ -389,37 +389,4 @@ public class WifiPowerStatsProcessor extends PowerStatsProcessor { } } } - - @Override - String deviceStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - if (mHasWifiPowerController) { - return "rx: " + mStatsLayout.getDeviceRxTime(stats) - + " tx: " + mStatsLayout.getDeviceTxTime(stats) - + " scan: " + mStatsLayout.getDeviceScanTime(stats) - + " idle: " + mStatsLayout.getDeviceIdleTime(stats) - + " power: " + mStatsLayout.getDevicePowerEstimate(stats); - } else { - return "active: " + mStatsLayout.getDeviceActiveTime(stats) - + " scan: " + mStatsLayout.getDeviceBasicScanTime(stats) - + " batched-scan: " + mStatsLayout.getDeviceBatchedScanTime(stats) - + " power: " + mStatsLayout.getDevicePowerEstimate(stats); - } - } - - @Override - String stateStatsToString(PowerStats.Descriptor descriptor, int key, long[] stats) { - // Unsupported for this power component - return null; - } - - @Override - String uidStatsToString(PowerStats.Descriptor descriptor, long[] stats) { - unpackPowerStatsDescriptor(descriptor); - return "rx: " + mStatsLayout.getUidRxPackets(stats) - + " tx: " + mStatsLayout.getUidTxPackets(stats) - + " scan: " + mStatsLayout.getUidScanTime(stats) - + " batched-scan: " + mStatsLayout.getUidBatchedScanTime(stats) - + " power: " + mStatsLayout.getUidPowerEstimate(stats); - } } 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/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java index e3e478d5ce9f..c1b825b3f8d1 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -49,10 +49,13 @@ import static android.util.MathUtils.constrain; import static android.view.Display.HdrCapabilities.HDR_TYPE_INVALID; import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; -import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__QS_SHORTCUT_TYPE__QUICK_SETTINGS; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_BUTTON; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_FLOATING_MENU; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_GESTURE; +import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__NOT_OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.DATA_USAGE_BYTES_TRANSFER__OPPORTUNISTIC_DATA_SUB__OPPORTUNISTIC; import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__GEO; @@ -61,7 +64,6 @@ import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STA import static com.android.internal.util.FrameworkStatsLog.TIME_ZONE_DETECTOR_STATE__DETECTION_MODE__UNKNOWN; import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem; import static com.android.server.stats.Flags.addMobileBytesTransferByProcStatePuller; -import static com.android.server.stats.Flags.statsPullNetworkStatsManagerInitOrderFix; import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs; import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs; import static com.android.server.stats.pull.ProcfsMemoryUtil.getProcessCmdlines; @@ -431,12 +433,6 @@ public class StatsPullAtomService extends SystemService { public static final boolean ENABLE_MOBILE_DATA_STATS_AGGREGATED_PULLER = addMobileBytesTransferByProcStatePuller(); - /** - * Whether or not to enable the mNetworkStatsManager initialization order fix - */ - private static final boolean ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX = - statsPullNetworkStatsManagerInitOrderFix(); - // Puller locks private final Object mDataBytesTransferLock = new Object(); private final Object mBluetoothBytesTransferLock = new Object(); @@ -799,7 +795,7 @@ public class StatsPullAtomService extends SystemService { case FrameworkStatsLog.KEYSTORE2_CRASH_STATS: return pullKeystoreAtoms(atomTag, data); case FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS: - return pullAccessibilityShortcutStatsLocked(atomTag, data); + return pullAccessibilityShortcutStatsLocked(data); case FrameworkStatsLog.ACCESSIBILITY_FLOATING_MENU_STATS: return pullAccessibilityFloatingMenuStatsLocked(atomTag, data); case FrameworkStatsLog.MEDIA_CAPABILITIES: @@ -840,9 +836,7 @@ public class StatsPullAtomService extends SystemService { registerEventListeners(); }); } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) { - if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) { - initNetworkStatsManager(); - } + initNetworkStatsManager(); BackgroundThread.getHandler().post(() -> { // Network stats related pullers can only be initialized after service is ready. initAndRegisterNetworkStatsPullers(); @@ -863,9 +857,6 @@ public class StatsPullAtomService extends SystemService { mContext.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mStatsSubscriptionsListener = new StatsSubscriptionsListener(mSubscriptionManager); mStorageManager = (StorageManager) mContext.getSystemService(StorageManager.class); - if (!ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) { - initNetworkStatsManager(); - } // Initialize DiskIO mStoragedUidIoStatsReader = new StoragedUidIoStatsReader(); @@ -1047,10 +1038,8 @@ public class StatsPullAtomService extends SystemService { */ @NonNull private NetworkStatsManager getNetworkStatsManager() { - if (ENABLE_NETWORK_STATS_MANAGER_INIT_ORDER_FIX) { - if (mNetworkStatsManager == null) { - throw new IllegalStateException("NetworkStatsManager is not ready"); - } + if (mNetworkStatsManager == null) { + throw new IllegalStateException("NetworkStatsManager is not ready"); } return mNetworkStatsManager; } @@ -4774,7 +4763,10 @@ public class StatsPullAtomService extends SystemService { } } - int pullAccessibilityShortcutStatsLocked(int atomTag, List<StatsEvent> pulledData) { + /** + * Pulls ACCESSIBILITY_SHORTCUT_STATS atom + */ + int pullAccessibilityShortcutStatsLocked(List<StatsEvent> pulledData) { UserManager userManager = mContext.getSystemService(UserManager.class); if (userManager == null) { return StatsManager.PULL_SKIP; @@ -4782,10 +4774,6 @@ public class StatsPullAtomService extends SystemService { final long token = Binder.clearCallingIdentity(); try { final ContentResolver resolver = mContext.getContentResolver(); - final int hardware_shortcut_type = - FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__VOLUME_KEY; - final int triple_tap_shortcut = - FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__TRIPLE_TAP; for (UserInfo userInfo : userManager.getUsers()) { final int userId = userInfo.getUserHandle().getIdentifier(); @@ -4803,15 +4791,22 @@ public class StatsPullAtomService extends SystemService { final int hardware_shortcut_service_num = countAccessibilityServices( hardware_shortcut_list); + final String qs_shortcut_list = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_QS_TARGETS, userId); + final boolean qs_shortcut_enabled = !TextUtils.isEmpty(qs_shortcut_list); + // only allow magnification to use it for now final int triple_tap_service_num = Settings.Secure.getIntForUser(resolver, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId); - - pulledData.add( - FrameworkStatsLog.buildStatsEvent(atomTag, - software_shortcut_type, software_shortcut_service_num, - hardware_shortcut_type, hardware_shortcut_service_num, - triple_tap_shortcut, triple_tap_service_num)); + pulledData.add(FrameworkStatsLog.buildStatsEvent( + FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_STATS, + software_shortcut_type, software_shortcut_service_num, + ACCESSIBILITY_SHORTCUT_STATS__HARDWARE_SHORTCUT_TYPE__VOLUME_KEY, + hardware_shortcut_service_num, + ACCESSIBILITY_SHORTCUT_STATS__GESTURE_SHORTCUT_TYPE__TRIPLE_TAP, + triple_tap_service_num, + ACCESSIBILITY_SHORTCUT_STATS__QS_SHORTCUT_TYPE__QUICK_SETTINGS, + qs_shortcut_enabled)); } } } catch (RuntimeException e) { @@ -5150,16 +5145,19 @@ public class StatsPullAtomService extends SystemService { Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId); final String hardware_shortcut_list = Settings.Secure.getStringForUser(resolver, Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId); + final String qs_shortcut_list = Settings.Secure.getStringForUser(resolver, + Settings.Secure.ACCESSIBILITY_QS_TARGETS, userId); final boolean hardware_shortcut_dialog_shown = Settings.Secure.getIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0, userId) == 1; final boolean software_shortcut_enabled = !TextUtils.isEmpty(software_shortcut_list); final boolean hardware_shortcut_enabled = hardware_shortcut_dialog_shown && !TextUtils.isEmpty(hardware_shortcut_list); + final boolean qs_shortcut_enabled = !TextUtils.isEmpty(qs_shortcut_list); final boolean triple_tap_shortcut_enabled = Settings.Secure.getIntForUser(resolver, Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0, userId) == 1; return software_shortcut_enabled || hardware_shortcut_enabled - || triple_tap_shortcut_enabled; + || triple_tap_shortcut_enabled || qs_shortcut_enabled; } private boolean isAccessibilityFloatingMenuUser(Context context, @UserIdInt int userId) { @@ -5176,13 +5174,13 @@ public class StatsPullAtomService extends SystemService { private int convertToAccessibilityShortcutType(int shortcutType) { switch (shortcutType) { case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR: - return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_BUTTON; + return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_BUTTON; case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU: - return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_FLOATING_MENU; + return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_FLOATING_MENU; case Settings.Secure.ACCESSIBILITY_BUTTON_MODE_GESTURE: - return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_GESTURE; + return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__A11Y_GESTURE; default: - return ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__UNKNOWN_TYPE; + return ACCESSIBILITY_SHORTCUT_STATS__SOFTWARE_SHORTCUT_TYPE__UNKNOWN_TYPE; } } diff --git a/services/core/java/com/android/server/stats/stats_flags.aconfig b/services/core/java/com/android/server/stats/stats_flags.aconfig index c479c6d11164..6faa2737ac30 100644 --- a/services/core/java/com/android/server/stats/stats_flags.aconfig +++ b/services/core/java/com/android/server/stats/stats_flags.aconfig @@ -8,11 +8,3 @@ flag { bug: "309512867" is_fixed_read_only: true } - -flag { - name: "stats_pull_network_stats_manager_init_order_fix" - namespace: "statsd" - description: "Fix the mNetworkStatsManager initialization order" - bug: "331989853" - is_fixed_read_only: true -} 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/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java index ad2c3e83b041..3579246b660f 100644 --- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java +++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java @@ -225,7 +225,7 @@ final class ServiceConfigAccessorImpl implements ServiceConfigAccessor { @NonNull TimeConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) { Objects.requireNonNull(requestedConfiguration); - TimeCapabilitiesAndConfig capabilitiesAndConfig = getCurrentUserConfigurationInternal() + TimeCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId) .createCapabilitiesAndConfig(bypassUserPolicyChecks); TimeCapabilities capabilities = capabilitiesAndConfig.getCapabilities(); TimeConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration(); diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java index 36192537493a..474253223628 100644 --- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java +++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java @@ -115,6 +115,10 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { // validation failure. private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT = 12; + /** Carriers can disable the detector by setting the threshold to -1 */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR = -1; + private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20; // By default, there's no maximum limit enforced @@ -271,7 +275,10 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { // When multiple parallel inbound transforms are created, NetworkMetricMonitor will be // enabled on the last one as a sample mInboundTransform = inboundTransform; - start(); + + if (!Flags.allowDisableIpsecLossDetector() || canStart()) { + start(); + } } @Override @@ -284,6 +291,14 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig); mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig); } + + if (Flags.allowDisableIpsecLossDetector() && canStart() != isStarted()) { + if (canStart()) { + start(); + } else { + stop(); + } + } } @Override @@ -298,6 +313,12 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor { mHandler.postDelayed(new PollIpSecStateRunnable(), mCancellationToken, 0L); } + private boolean canStart() { + return mInboundTransform != null + && mPacketLossRatePercentThreshold + != IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR; + } + @Override protected void start() { super.start(); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java index b19bc7d43920..80f1125a4ecf 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperCropper.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperCropper.java @@ -150,7 +150,7 @@ public class WallpaperCropper { Rect landscapeCrop = getCrop(rotatedDisplaySize, bitmapSize, suggestedCrops, rtl); landscapeCrop = noParallax(landscapeCrop, rotatedDisplaySize, bitmapSize, rtl); // compute the crop on portrait at the center of the landscape crop - crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, rtl, ADD); + crop = getAdjustedCrop(landscapeCrop, bitmapSize, displaySize, false, ADD); // add some parallax (until the border of the landscape crop without parallax) if (rtl) { @@ -160,7 +160,7 @@ public class WallpaperCropper { } } - return getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD); + return getAdjustedCrop(crop, bitmapSize, displaySize, true, ADD); } // If any suggested crop is invalid, fallback to case 1 @@ -176,7 +176,7 @@ public class WallpaperCropper { // Case 2: if the orientation exists in the suggested crops, adjust the suggested crop Rect suggestedCrop = suggestedCrops.get(orientation); if (suggestedCrop != null) { - return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, rtl, ADD); + return getAdjustedCrop(suggestedCrop, bitmapSize, displaySize, true, ADD); } // Case 3: if we have the 90° rotated orientation in the suggested crops, reuse it and @@ -188,7 +188,7 @@ public class WallpaperCropper { if (suggestedCrop != null) { // only keep the visible part (without parallax) Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); - return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, BALANCE); + return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, BALANCE); } // Case 4: if the device is a foldable, if we're looking for a folded orientation and have @@ -200,13 +200,13 @@ public class WallpaperCropper { // compute the visible part (without parallax) of the unfolded screen Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); // compute the folded crop, at the center of the crop of the unfolded screen - Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, REMOVE); + Rect res = getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, REMOVE); // if we removed some width, add it back to add a parallax effect if (res.width() < adjustedCrop.width()) { if (rtl) res.left = Math.min(res.left, adjustedCrop.left); else res.right = Math.max(res.right, adjustedCrop.right); // use getAdjustedCrop(parallax=true) to make sure we don't exceed MAX_PARALLAX - res = getAdjustedCrop(res, bitmapSize, displaySize, true, rtl, ADD); + res = getAdjustedCrop(res, bitmapSize, displaySize, true, ADD); } return res; } @@ -220,7 +220,7 @@ public class WallpaperCropper { if (suggestedCrop != null) { // only keep the visible part (without parallax) Rect adjustedCrop = noParallax(suggestedCrop, suggestedDisplaySize, bitmapSize, rtl); - return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, rtl, ADD); + return getAdjustedCrop(adjustedCrop, bitmapSize, displaySize, false, ADD); } // Case 6: for a foldable device, try to combine case 3 + case 4 or 5: @@ -255,7 +255,7 @@ public class WallpaperCropper { @VisibleForTesting static Rect noParallax(Rect crop, Point displaySize, Point bitmapSize, boolean rtl) { if (displaySize == null) return crop; - Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, rtl, ADD); + Rect adjustedCrop = getAdjustedCrop(crop, bitmapSize, displaySize, true, ADD); // only keep the visible part (without parallax) float suggestedDisplayRatio = 1f * displaySize.x / displaySize.y; int widthToRemove = (int) (adjustedCrop.width() @@ -272,7 +272,7 @@ public class WallpaperCropper { * Adjust a given crop: * <ul> * <li>If parallax = true, make sure we have a parallax of at most {@link #MAX_PARALLAX}, - * by removing content from the right (or left if RTL layout) if necessary. + * by removing content from both sides if necessary. * <li>If parallax = false, make sure we do not have additional width for parallax. If we * have additional width for parallax, remove half of the additional width on both sides. * <li>Make sure the crop fills the screen, i.e. that the width/height ratio of the crop @@ -282,7 +282,7 @@ public class WallpaperCropper { */ @VisibleForTesting static Rect getAdjustedCrop(Rect crop, Point bitmapSize, Point screenSize, - boolean parallax, boolean rtl, int mode) { + boolean parallax, int mode) { Rect adjustedCrop = new Rect(crop); float cropRatio = ((float) crop.width()) / crop.height(); float screenRatio = ((float) screenSize.x) / screenSize.y; @@ -297,8 +297,7 @@ public class WallpaperCropper { Rect rotatedCrop = new Rect(newLeft, newTop, newRight, newBottom); Point rotatedBitmap = new Point(bitmapSize.y, bitmapSize.x); Point rotatedScreen = new Point(screenSize.y, screenSize.x); - Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, rtl, - mode); + Rect rect = getAdjustedCrop(rotatedCrop, rotatedBitmap, rotatedScreen, false, mode); int resultLeft = rect.top; int resultRight = resultLeft + rect.height(); int resultTop = rotatedBitmap.x - rect.right; @@ -313,10 +312,13 @@ public class WallpaperCropper { adjustedCrop.right -= widthToRemove / 2 + widthToRemove % 2; } } else { - // TODO (b/281648899) the third case is not always correct, fix that. + // Note: the third case when MODE == BALANCE, -W + sqrt(W * H * R), is the width to add + // so that, when removing the appropriate height, we get a bitmap of aspect ratio R and + // total surface of W * H. In other words it is the width to add to get the desired + // aspect ratio R, while preserving the total number of pixels W * H. int widthToAdd = mode == REMOVE ? 0 - : mode == ADD ? (int) (0.5 + crop.height() * screenRatio - crop.width()) - : (int) (0.5 + crop.height() - crop.width()); + : mode == ADD ? (int) (crop.height() * screenRatio - crop.width()) + : (int) (-crop.width() + Math.sqrt(crop.width() * crop.height() * screenRatio)); int availableWidth = bitmapSize.x - crop.width(); if (availableWidth >= widthToAdd) { int widthToAddLeft = widthToAdd / 2; diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index d20b3b22d778..f8eb78914857 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -3646,7 +3646,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // System wallpaper does not support multiple displays, attach this display to // the fallback wallpaper. - if (mFallbackWallpaper != null) { + if (mFallbackWallpaper != null && mFallbackWallpaper + .connection != null) { final DisplayConnector connector = mFallbackWallpaper .connection.getDisplayConnectorOrCreate(displayId); if (connector == null) return; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index e280bdc7780b..5be5bc5e3952 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -984,36 +984,26 @@ final class AccessibilityController { Region touchableRegion = mTempRegion3; windowState.getTouchableRegion(touchableRegion); Region windowBounds = mTempRegion2; - if (Flags.useWindowOriginalTouchableRegionWhenMagnificationRecomputeBounds()) { - // For b/323366243, if using the bounds from touchableRegion.getBounds, in - // non-magnifiable windowBounds computation, part of the non-touchableRegion - // may be included into nonMagnifiedBounds. This will make users lose - // the magnification control on mis-included areas. - // Therefore, to prevent the above issue, we change to use the window exact - // touchableRegion in magnificationRegion computation. - // Like the original approach, the touchableRegion is in non-magnified display - // space, so first we need to offset the region by the windowFrames bounds, then - // apply the transform matrix to the region to get the exact region in magnified - // display space. - // TODO: For a long-term plan, since touchable regions provided by WindowState - // doesn't actually reflect the real touchable regions on display, we should - // delete the WindowState dependency and migrate to use the touchableRegion - // from WindowInfoListener data. (b/330653961) - touchableRegion.translate(-windowState.getFrame().left, - -windowState.getFrame().top); - applyMatrixToRegion(matrix, touchableRegion); - windowBounds.set(touchableRegion); - } else { - Rect touchableFrame = mTempRect1; - touchableRegion.getBounds(touchableFrame); - RectF windowFrame = mTempRectF; - windowFrame.set(touchableFrame); - windowFrame.offset(-windowState.getFrame().left, - -windowState.getFrame().top); - matrix.mapRect(windowFrame); - windowBounds.set((int) windowFrame.left, (int) windowFrame.top, - (int) windowFrame.right, (int) windowFrame.bottom); - } + + // For b/323366243, if using the bounds from touchableRegion.getBounds, in + // non-magnifiable windowBounds computation, part of the non-touchableRegion + // may be included into nonMagnifiedBounds. This will make users lose + // the magnification control on mis-included areas. + // Therefore, to prevent the above issue, we change to use the window exact + // touchableRegion in magnificationRegion computation. + // Like the original approach, the touchableRegion is in non-magnified display + // space, so first we need to offset the region by the windowFrames bounds, then + // apply the transform matrix to the region to get the exact region in magnified + // display space. + // TODO: For a long-term plan, since touchable regions provided by WindowState + // doesn't actually reflect the real touchable regions on display, we should + // delete the WindowState dependency and migrate to use the touchableRegion + // from WindowInfoListener data. (b/330653961) + touchableRegion.translate(-windowState.getFrame().left, + -windowState.getFrame().top); + applyMatrixToRegion(matrix, touchableRegion); + windowBounds.set(touchableRegion); + // Only update new regions Region portionOfWindowAlreadyAccountedFor = mTempRegion3; portionOfWindowAlreadyAccountedFor.set(mMagnificationRegion); diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index f6afc52fd8d8..3393d3e049e1 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -150,7 +150,11 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener { @Override public void onWindowInfosChanged(InputWindowHandle[] windowHandles, DisplayInfo[] displayInfos) { - mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos)); + if (com.android.server.accessibility.Flags.removeOnWindowInfosChangedHandler()) { + onWindowInfosChangedInternal(windowHandles, displayInfos); + } else { + mHandler.post(() -> onWindowInfosChangedInternal(windowHandles, displayInfos)); + } } private void onWindowInfosChangedInternal(InputWindowHandle[] windowHandles, diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index c5683f31f3e7..c9395daff974 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -957,6 +957,7 @@ class ActivityClientController extends IActivityClientController.Stub { public boolean enterPictureInPictureMode(IBinder token, final PictureInPictureParams params) { final long origId = Binder.clearCallingIdentity(); try { + ensureSetPipAspectRatioQuotaTracker(); synchronized (mGlobalLock) { final ActivityRecord r = ensureValidPictureInPictureActivityParams( "enterPictureInPictureMode", token, params); @@ -971,6 +972,7 @@ class ActivityClientController extends IActivityClientController.Stub { public void setPictureInPictureParams(IBinder token, final PictureInPictureParams params) { final long origId = Binder.clearCallingIdentity(); try { + ensureSetPipAspectRatioQuotaTracker(); synchronized (mGlobalLock) { final ActivityRecord r = ensureValidPictureInPictureActivityParams( "setPictureInPictureParams", token, params); @@ -1023,6 +1025,19 @@ class ActivityClientController extends IActivityClientController.Stub { } /** + * Initialize the {@link #mSetPipAspectRatioQuotaTracker} if applicable, which should happen + * out of {@link #mGlobalLock} to avoid deadlock (AM lock is used in QuotaTrack ctor). + */ + private void ensureSetPipAspectRatioQuotaTracker() { + if (mSetPipAspectRatioQuotaTracker == null) { + mSetPipAspectRatioQuotaTracker = new CountQuotaTracker(mContext, + Categorizer.SINGLE_CATEGORIZER); + mSetPipAspectRatioQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, + SET_PIP_ASPECT_RATIO_LIMIT, SET_PIP_ASPECT_RATIO_TIME_WINDOW_MS); + } + } + + /** * Checks the state of the system and the activity associated with the given {@param token} to * verify that picture-in-picture is supported for that activity. * @@ -1049,12 +1064,6 @@ class ActivityClientController extends IActivityClientController.Stub { // Rate limit how frequent an app can request aspect ratio change via // Activity#setPictureInPictureParams final int userId = UserHandle.getCallingUserId(); - if (mSetPipAspectRatioQuotaTracker == null) { - mSetPipAspectRatioQuotaTracker = new CountQuotaTracker(mContext, - Categorizer.SINGLE_CATEGORIZER); - mSetPipAspectRatioQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY, - SET_PIP_ASPECT_RATIO_LIMIT, SET_PIP_ASPECT_RATIO_TIME_WINDOW_MS); - } if (r.pictureInPictureArgs.hasSetAspectRatio() && params.hasSetAspectRatio() && !r.pictureInPictureArgs.getAspectRatio().equals( diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 6ec557a15134..b3208bfcc93d 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -88,6 +88,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.app.ActivityOptions.SourceInfo; +import android.app.ApplicationStartInfo; import android.app.CameraCompatTaskInfo.CameraCompatControlState; import android.app.WaitResult; import android.app.WindowConfiguration.WindowingMode; @@ -845,6 +846,16 @@ class ActivityMetricsLogger { && !r.mTransitionController.isCollecting(r))) { done(false /* abort */, info, "notifyWindowsDrawn", timestampNs); } + + if (android.app.Flags.appStartInfoTimestamps()) { + // Log here to match StatsD for time to first frame. + mLoggerHandler.post( + () -> mSupervisor.mService.mWindowManager.mAmInternal.addStartInfoTimestamp( + ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME, + timestampNs, r.getUid(), r.getPid(), + info.mLastLaunchedActivity.mUserId)); + } + return infoSnapshot; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e814f17bedef..8253a957d231 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -77,7 +77,6 @@ import static android.content.pm.ActivityInfo.FLAG_NO_HISTORY; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static android.content.pm.ActivityInfo.FLAG_STATE_NOT_NEEDED; import static android.content.pm.ActivityInfo.FLAG_TURN_SCREEN_ON; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; @@ -87,6 +86,7 @@ import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; @@ -121,6 +121,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.WindowManager.ACTIVITY_EMBEDDING_GUARD_WITH_ANDROID_15; +import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15; import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; @@ -128,7 +129,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED; import static android.view.WindowManager.PROPERTY_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING_STATE_SHARING; -import static android.view.WindowManager.ENABLE_ACTIVITY_EMBEDDING_FOR_ANDROID_15; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND; import static android.view.WindowManager.TRANSIT_OLD_UNSET; @@ -661,6 +661,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private final TaskFragment.ConfigOverrideHint mResolveConfigHint; + private final boolean mOptOutEdgeToEdge; + private static ConstrainDisplayApisConfig sConstrainDisplayApisConfig; boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session @@ -683,6 +685,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // it references to gets removed. This should also be cleared when we move out of pip. private Task mLastParentBeforePip; + // The token of the previous TaskFragment parent of this embedded ActivityRecord when it is + // reparented to a new Task due to picture-in-picture. + // Note that the TaskFragment may be finished and no longer attached in WM hierarchy. + @Nullable + private IBinder mLastEmbeddedParentTfTokenBeforePip; + // Only set if this instance is a launch-into-pip Activity, points to the // host Activity the launch-into-pip Activity is originated from. private ActivityRecord mLaunchIntoPipHostActivity; @@ -1807,6 +1815,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLastTaskFragmentOrganizerBeforePip = organizedTf != null ? organizedTf.getTaskFragmentOrganizer() : null; + if (organizedTf != null + // Not necessary for content pip. + && launchIntoPipHostActivity == null) { + mLastEmbeddedParentTfTokenBeforePip = organizedTf.getFragmentToken(); + } } void clearLastParentBeforePip() { @@ -1816,12 +1829,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } mLaunchIntoPipHostActivity = null; mLastTaskFragmentOrganizerBeforePip = null; + mLastEmbeddedParentTfTokenBeforePip = null; } @Nullable Task getLastParentBeforePip() { return mLastParentBeforePip; } + @Nullable IBinder getLastEmbeddedParentTfTokenBeforePip() { + return mLastEmbeddedParentTfTokenBeforePip; + } + @Nullable ActivityRecord getLaunchIntoPipHostActivity() { return mLaunchIntoPipHostActivity; } @@ -2164,9 +2182,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A || ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false); mStyleFillsParent = mOccludesParent; noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false); + mOptOutEdgeToEdge = ent.array.getBoolean( + R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false); } else { mStyleFillsParent = mOccludesParent = true; noDisplay = false; + mOptOutEdgeToEdge = false; } if (options != null) { @@ -3272,8 +3293,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mOccludesParent = occludesParent; setMainWindowOpaque(occludesParent); - if (changed && task != null && !occludesParent) { - getRootTask().convertActivityToTranslucent(this); + if (changed && task != null) { + if (!occludesParent) { + getRootTask().convertActivityToTranslucent(this); + } else { + getRootTask().convertActivityFromTranslucent(this); + } } // Always ensure visibility if this activity doesn't occlude parent, so the // {@link #returningOptions} of the activity under this one can be applied in @@ -4266,6 +4291,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A getTaskFragment().cleanUpActivityReferences(this); clearLastParentBeforePip(); + // Abort and reset state if the scence transition is playing. + final Task rootTask = getRootTask(); + if (rootTask != null) { + rootTask.abortTranslucentActivityWaiting(this); + } + // Clean up the splash screen if it was still displayed. cleanUpSplashScreen(); @@ -5672,6 +5703,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (mTransitionController.inFinishingTransition(this)) { mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION; } + } else { + mTransitionChangeFlags &= ~FLAG_IS_OCCLUDED; } return; } @@ -6519,8 +6552,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // and the token could be null. return; } - if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) { - r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r); + if (r.mDisplayContent.mActivityRefresher != null) { + r.mDisplayContent.mActivityRefresher.onActivityRefreshed(r); } } @@ -8684,9 +8717,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (rotation == ROTATION_UNDEFINED && !isFixedRotationTransforming()) { rotation = mDisplayContent.getRotation(); } - if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds + if (!mOptOutEdgeToEdge && (!mResolveConfigHint.mUseOverrideInsetsForStableBounds || getCompatDisplayInsets() != null || isFloating(parentWindowingMode) - || rotation == ROTATION_UNDEFINED) { + || rotation == ROTATION_UNDEFINED)) { // If the insets configuration decoupled logic is not enabled for the app, or the app // already has a compat override, or the context doesn't contain enough info to // calculate the override, skip the override. @@ -10032,7 +10065,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo); } - notifyDisplayCompatPolicyAboutConfigurationChange( + notifyActivityRefresherAboutConfigurationChange( mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig); return true; } @@ -10099,18 +10132,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else { scheduleConfigurationChanged(newMergedOverrideConfig, newActivityWindowInfo); } - notifyDisplayCompatPolicyAboutConfigurationChange( + notifyActivityRefresherAboutConfigurationChange( mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig); return true; } - private void notifyDisplayCompatPolicyAboutConfigurationChange( + private void notifyActivityRefresherAboutConfigurationChange( Configuration newConfig, Configuration lastReportedConfig) { - if (mDisplayContent.mDisplayRotationCompatPolicy == null + if (mDisplayContent.mActivityRefresher == null || !shouldBeResumed(/* activeActivity */ null)) { return; } - mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging( + mDisplayContent.mActivityRefresher.onActivityConfigurationChanging( this, newConfig, lastReportedConfig); } @@ -10862,8 +10895,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); @@ -11109,7 +11144,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Otherwise, return the creation time of the top window. */ long getLastWindowCreateTime() { - final WindowState window = getWindow(win -> true); + final WindowState window = getWindow(alwaysTruePredicate()); return window != null && window.mAttrs.type != TYPE_BASE_APPLICATION ? window.getCreateTime() : createTime; diff --git a/services/core/java/com/android/server/wm/ActivityRefresher.java b/services/core/java/com/android/server/wm/ActivityRefresher.java new file mode 100644 index 000000000000..23a97089fd60 --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityRefresher.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; + +import android.annotation.NonNull; +import android.app.servertransaction.RefreshCallbackItem; +import android.app.servertransaction.ResumeActivityItem; +import android.content.res.Configuration; +import android.os.Handler; +import android.os.RemoteException; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; + +/** + * Class that refreshes the activity (through stop/pause -> resume) based on configuration change. + * + * <p>This class queries all of its {@link Evaluator}s and restarts the activity if any of them + * return {@code true} in {@link Evaluator#shouldRefreshActivity}. {@link ActivityRefresher} cycles + * through either stop or pause and then resume, based on the global config and per-app override. + */ +class ActivityRefresher { + // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The + // client process may not always report the event back to the server, such as process is + // crashed or got killed. + private static final long REFRESH_CALLBACK_TIMEOUT_MS = 2000L; + + @NonNull private final WindowManagerService mWmService; + @NonNull private final Handler mHandler; + @NonNull private final ArrayList<Evaluator> mEvaluators = new ArrayList<>(); + + ActivityRefresher(@NonNull WindowManagerService wmService, @NonNull Handler handler) { + mWmService = wmService; + mHandler = handler; + } + + void addEvaluator(@NonNull Evaluator evaluator) { + mEvaluators.add(evaluator); + } + + void removeEvaluator(@NonNull Evaluator evaluator) { + mEvaluators.remove(evaluator); + } + + /** + * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle. + * This allows to clear cached values in apps (e.g. display or camera rotation) that influence + * camera preview and can lead to sideways or stretching issues persisting even after force + * rotation. + */ + void onActivityConfigurationChanging(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { + if (!shouldRefreshActivity(activity, newConfig, lastReportedConfig)) { + return; + } + + final boolean cycleThroughStop = + mWmService.mLetterboxConfiguration + .isCameraCompatRefreshCycleThroughStopEnabled() + && !activity.mLetterboxUiController + .shouldRefreshActivityViaPauseForCameraCompat(); + + activity.mLetterboxUiController.setIsRefreshRequested(true); + ProtoLog.v(WM_DEBUG_STATES, + "Refreshing activity for freeform camera compatibility treatment, " + + "activityRecord=%s", activity); + final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain( + activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain( + activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + try { + activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems( + activity.app.getThread(), refreshCallbackItem, resumeActivityItem); + mHandler.postDelayed(() -> { + synchronized (mWmService.mGlobalLock) { + onActivityRefreshed(activity); + } + }, REFRESH_CALLBACK_TIMEOUT_MS); + } catch (RemoteException e) { + activity.mLetterboxUiController.setIsRefreshRequested(false); + } + } + + boolean isActivityRefreshing(@NonNull ActivityRecord activity) { + return activity.mLetterboxUiController.isRefreshRequested(); + } + + void onActivityRefreshed(@NonNull ActivityRecord activity) { + // TODO(b/333060789): can we tell that refresh did not happen by observing the activity + // state? + activity.mLetterboxUiController.setIsRefreshRequested(false); + } + + private boolean shouldRefreshActivity(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { + return mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled() + && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat() + && ArrayUtils.find(mEvaluators.toArray(), evaluator -> + ((Evaluator) evaluator) + .shouldRefreshActivity(activity, newConfig, lastReportedConfig)) != null; + } + + /** + * Interface for classes that would like to refresh the recently updated activity, based on the + * configuration change. + */ + interface Evaluator { + boolean shouldRefreshActivity(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig); + } +} diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java index 3609837f417b..ed07afd2eab5 100644 --- a/services/core/java/com/android/server/wm/ActivitySnapshotCache.java +++ b/services/core/java/com/android/server/wm/ActivitySnapshotCache.java @@ -30,10 +30,12 @@ class ActivitySnapshotCache extends SnapshotCache<ActivityRecord> { @Override void putSnapshot(ActivityRecord ar, TaskSnapshot snapshot) { final int hasCode = System.identityHashCode(ar); + snapshot.addReference(TaskSnapshot.REFERENCE_CACHE); synchronized (mLock) { final CacheEntry entry = mRunningCache.get(hasCode); if (entry != null) { mAppIdMap.remove(entry.topApp); + entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE); } mAppIdMap.put(ar, hasCode); mRunningCache.put(hasCode, new CacheEntry(snapshot, ar)); diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 330336760413..72b854be74bd 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2113,7 +2113,6 @@ class ActivityStarter { if (hostTask == null || targetTask != hostTask) { return EMBEDDING_DISALLOWED_NEW_TASK; } - return taskFragment.isAllowedToEmbedActivity(starting); } @@ -2733,7 +2732,7 @@ class ActivityStarter { // If a target task is specified, try to reuse that one if (mOptions != null && mOptions.getLaunchTaskId() != INVALID_TASK_ID) { Task launchTask = mRootWindowContainer.anyTaskForId(mOptions.getLaunchTaskId()); - if (launchTask != null) { + if (launchTask != null && launchTask.isLeafTask()) { return launchTask; } return null; @@ -2961,23 +2960,9 @@ class ActivityStarter { sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult); } } else { - TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null; + TaskFragment candidateTf = mAddingToTaskFragment; if (candidateTf == null) { - // Puts the activity on the top-most non-isolated navigation TF, unless the - // activity is launched from the same TF. - final TaskFragment sourceTaskFragment = - mSourceRecord != null ? mSourceRecord.getTaskFragment() : null; - final ActivityRecord top = task.getActivity(r -> { - if (!r.canBeTopRunning()) { - return false; - } - final TaskFragment taskFragment = r.getTaskFragment(); - return !taskFragment.isIsolatedNav() || (sourceTaskFragment != null - && sourceTaskFragment == taskFragment); - }); - if (top != null) { - candidateTf = top.getTaskFragment(); - } + candidateTf = findCandidateTaskFragment(task); } if (candidateTf != null && candidateTf.isEmbedded() && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) { @@ -2995,6 +2980,50 @@ class ActivityStarter { } /** + * Finds a candidate TaskFragment in {@code task} to launch activity, or returns {@code null} + * if there's no such a TaskFragment. + */ + @Nullable + private TaskFragment findCandidateTaskFragment(@NonNull Task task) { + final TaskFragment sourceTaskFragment = + mSourceRecord != null ? mSourceRecord.getTaskFragment() : null; + for (int i = task.getChildCount() - 1; i >= 0; --i) { + final WindowContainer<?> wc = task.getChildAt(i); + final ActivityRecord activity = wc.asActivityRecord(); + if (activity != null) { + if (activity.finishing) { + continue; + } + // Early return if the top child is an Activity. + return null; + } + final TaskFragment taskFragment = wc.asTaskFragment(); + if (taskFragment == null || taskFragment.isRemovalRequested()) { + // Skip if the TaskFragment is going to be finished. + continue; + } + if (taskFragment.getActivity(ActivityRecord::canBeTopRunning) == null) { + // Skip if there's no activity in this TF can be top running. + continue; + } + if (taskFragment.isIsolatedNav()) { + // Stop here if we reach an isolated navigated TF. + return null; + } + if (sourceTaskFragment != null && sourceTaskFragment == taskFragment) { + // Choose the taskFragment launched from even if it's pinned. + return taskFragment; + } + if (taskFragment.isPinned()) { + // Skip the pinned TaskFragment. + continue; + } + return taskFragment; + } + return null; + } + + /** * Notifies the client side that {@link #mStartActivity} cannot be embedded to * {@code taskFragment}. */ diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index f3e1dfb3cabd..3aa63af014c8 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; @@ -4381,7 +4383,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly, - boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId) { + boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId, + long timeout) { ArrayList<ActivityRecord> activities; synchronized (mGlobalLock) { @@ -4426,7 +4429,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } } - dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll); + dumpActivity(" ", fd, pw, activities.get(i), newArgs, dumpAll, timeout); } if (!printedAnything) { // Typically happpens when no task matches displayIdFilter @@ -4440,7 +4443,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { * there is a thread associated with the activity. */ private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw, - ActivityRecord r, String[] args, boolean dumpAll) { + ActivityRecord r, String[] args, boolean dumpAll, long timeout) { String innerPrefix = prefix + " "; IApplicationThread appThread = null; synchronized (mGlobalLock) { @@ -4471,7 +4474,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { pw.flush(); try (TransferPipe tp = new TransferPipe()) { appThread.dumpActivity(tp.getWriteFd(), r.token, innerPrefix, args); - tp.go(fd); + tp.go(fd, timeout); } catch (IOException e) { pw.println(innerPrefix + "Failure while dumping the activity: " + e); } catch (RemoteException e) { @@ -6970,7 +6973,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { boolean dumpFocusedRootTaskOnly, int displayIdFilter, @UserIdInt int userId) { return ActivityTaskManagerService.this.dumpActivity(fd, pw, name, args, opti, dumpAll, - dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, displayIdFilter, userId); + dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, displayIdFilter, userId, + /* timeout= */ 5000); } @Override @@ -7398,7 +7402,26 @@ 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( + "persist.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/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java index e7fb26550886..24d4be83c82b 100644 --- a/services/core/java/com/android/server/wm/AsyncRotationController.java +++ b/services/core/java/com/android/server/wm/AsyncRotationController.java @@ -288,6 +288,17 @@ class AsyncRotationController extends FadeAnimationController implements Consume final SurfaceControl.Transaction t = windowToken.getSyncTransaction(); clearTransform(t, op.mLeash); } + // The insets position may be frozen by shouldFreezeInsetsPosition(), so refresh the + // position to the latest state when it is ready to show in new rotation. + if (mTransitionOp == OP_APP_SWITCH) { + for (int i = windowToken.getChildCount() - 1; i >= 0; i--) { + final WindowState w = windowToken.getChildAt(i); + final InsetsSourceProvider insetsProvider = w.getControllableInsetProvider(); + if (insetsProvider != null) { + insetsProvider.updateInsetsControlPosition(w); + } + } + } } private static void clearTransform(SurfaceControl.Transaction t, SurfaceControl sc) { 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/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index c9703d871431..0e4f0335118d 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -1732,7 +1732,10 @@ class BackNavigationController { // The activity was detached from hierarchy. return; } - activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp(); + + if (activity.mDisplayContent.isFixedRotationLaunchingApp(activity)) { + activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp(); + } // Restore the launch-behind state. activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token); diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 0e446b8eaf8c..f7910b08b1e2 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityOptions.BackgroundActivityStartMode; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -39,6 +40,7 @@ import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONL import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel; import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS; import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg; +import static com.android.window.flags.Flags.balImprovedMetrics; import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck; import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator; import static com.android.window.flags.Flags.balRequireOptInSameUid; @@ -104,6 +106,7 @@ public class BackgroundActivityStartController { static final String AUTO_OPT_IN_NOT_PENDING_INTENT = "notPendingIntent"; static final String AUTO_OPT_IN_CALL_FOR_RESULT = "callForResult"; static final String AUTO_OPT_IN_SAME_UID = "sameUid"; + static final String AUTO_OPT_IN_COMPAT = "compatibility"; /** If enabled the creator will not allow BAL on its behalf by default. */ @ChangeId @@ -302,6 +305,10 @@ public class BackgroundActivityStartController { } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) { mAutoOptInReason = AUTO_OPT_IN_SAME_UID; mAutoOptInCaller = false; + } else if (realCallerBackgroundActivityStartMode + == MODE_BACKGROUND_ACTIVITY_START_COMPAT) { + mAutoOptInReason = AUTO_OPT_IN_COMPAT; + mAutoOptInCaller = false; } else { mAutoOptInReason = null; mAutoOptInCaller = false; @@ -805,14 +812,25 @@ public class BackgroundActivityStartController { * or {@link #BAL_BLOCK} if the launch should be blocked */ BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) { - int callingUid = state.mCallingUid; - int callingPid = state.mCallingPid; - final String callingPackage = state.mCallingPackage; - WindowProcessController callerApp = state.mCallerApp; + // This is used to block background activity launch even if the app is still + // visible to user after user clicking home button. + + // Normal apps with visible app window will be allowed to start activity if app switching + // is allowed, or apps like live wallpaper with non app visible window will be allowed. + final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW + || state.mAppSwitchState == APP_SWITCH_FG_ONLY; + if (appSwitchAllowedOrFg && state.mCallingUidHasAnyVisibleWindow) { + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, + /*background*/ false, "callingUid has visible window"); + } + if (mService.mActiveUids.hasNonAppVisibleWindow(state.mCallingUid)) { + return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW, + /*background*/ false, "callingUid has non-app visible window"); + } // don't abort for the most important UIDs - final int callingAppId = UserHandle.getAppId(callingUid); - if (callingUid == Process.ROOT_UID + final int callingAppId = UserHandle.getAppId(state.mCallingUid); + if (state.mCallingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID || callingAppId == Process.NFC_UID) { return new BalVerdict( @@ -821,7 +839,7 @@ public class BackgroundActivityStartController { } // Always allow home application to start activities. - if (isHomeApp(callingUid, callingPackage)) { + if (isHomeApp(state.mCallingUid, state.mCallingPackage)) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, /*background*/ false, "Home app"); @@ -836,67 +854,46 @@ public class BackgroundActivityStartController { "Active ime"); } - // This is used to block background activity launch even if the app is still - // visible to user after user clicking home button. - final int appSwitchState = mService.getBalAppSwitchesState(); - - // don't abort if the callingUid has a visible window or is a persistent system process - final int callingUidProcState = mService.mActiveUids.getUidState(callingUid); - final boolean callingUidHasAnyVisibleWindow = mService.hasActiveVisibleWindow(callingUid); - final boolean isCallingUidPersistentSystemProcess = - callingUidProcState <= ActivityManager.PROCESS_STATE_PERSISTENT_UI; - - // Normal apps with visible app window will be allowed to start activity if app switching - // is allowed, or apps like live wallpaper with non app visible window will be allowed. - final boolean appSwitchAllowedOrFg = - appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY; - if (appSwitchAllowedOrFg && callingUidHasAnyVisibleWindow) { - return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, - /*background*/ false, "callingUid has visible window"); - } - if (mService.mActiveUids.hasNonAppVisibleWindow(callingUid)) { - return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW, - /*background*/ false, "callingUid has non-app visible window"); - } - - if (isCallingUidPersistentSystemProcess) { + // don't abort if the callingUid is a persistent system process + if (state.mIsCallingUidPersistentSystemProcess) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, /*background*/ false, "callingUid is persistent system process"); } // don't abort if the callingUid has START_ACTIVITIES_FROM_BACKGROUND permission - if (hasBalPermission(callingUid, callingPid)) { + if (hasBalPermission(state.mCallingUid, state.mCallingPid)) { return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, "START_ACTIVITIES_FROM_BACKGROUND permission granted"); } // don't abort if the caller has the same uid as the recents component - if (mSupervisor.mRecentTasks.isCallerRecents(callingUid)) { + if (mSupervisor.mRecentTasks.isCallerRecents(state.mCallingUid)) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, /*background*/ true, "Recents Component"); } // don't abort if the callingUid is the device owner - if (mService.isDeviceOwner(callingUid)) { + if (mService.isDeviceOwner(state.mCallingUid)) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, /*background*/ true, "Device Owner"); } // don't abort if the callingUid is a affiliated profile owner - if (mService.isAffiliatedProfileOwner(callingUid)) { + if (mService.isAffiliatedProfileOwner(state.mCallingUid)) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, /*background*/ true, "Affiliated Profile Owner"); } // don't abort if the callingUid has companion device - final int callingUserId = UserHandle.getUserId(callingUid); - if (mService.isAssociatedCompanionApp(callingUserId, callingUid)) { + final int callingUserId = UserHandle.getUserId(state.mCallingUid); + if (mService.isAssociatedCompanionApp(callingUserId, state.mCallingUid)) { return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, /*background*/ true, "Companion App"); } // don't abort if the callingUid has SYSTEM_ALERT_WINDOW permission - if (mService.hasSystemAlertWindowPermission(callingUid, callingPid, callingPackage)) { + if (mService.hasSystemAlertWindowPermission(state.mCallingUid, state.mCallingPid, + state.mCallingPackage)) { Slog.w( TAG, "Background activity start for " - + callingPackage + + state.mCallingPackage + " allowed because SYSTEM_ALERT_WINDOW permission is granted."); return new BalVerdict(BAL_ALLOW_SAW_PERMISSION, /*background*/ true, "SYSTEM_ALERT_WINDOW permission is granted"); @@ -905,7 +902,7 @@ public class BackgroundActivityStartController { // OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop if (isSystemExemptFlagEnabled() && mService.getAppOpsManager().checkOpNoThrow( AppOpsManager.OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, - callingUid, callingPackage) == AppOpsManager.MODE_ALLOWED) { + state.mCallingUid, state.mCallingPackage) == AppOpsManager.MODE_ALLOWED) { return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ true, "OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION appop is granted"); } @@ -914,7 +911,7 @@ public class BackgroundActivityStartController { // That's the case for PendingIntent-based starts, since the creator's process might not be // up and alive. // Don't abort if the callerApp or other processes of that uid are allowed in any way. - BalVerdict callerAppAllowsBal = checkProcessAllowsBal(callerApp, state); + BalVerdict callerAppAllowsBal = checkProcessAllowsBal(state.mCallerApp, state); if (callerAppAllowsBal.allows()) { return callerAppAllowsBal; } @@ -929,13 +926,6 @@ public class BackgroundActivityStartController { */ BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) { - if (state.isPendingIntentBalAllowedByPermission() - && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) { - return new BalVerdict(BAL_ALLOW_PERMISSION, - /*background*/ false, - "realCallingUid has BAL permission."); - } - // Normal apps with visible app window will be allowed to start activity if app switching // is allowed, or apps like live wallpaper with non app visible window will be allowed. // The home app can start apps even if app switches are usually disallowed. @@ -961,6 +951,13 @@ public class BackgroundActivityStartController { } } + if (state.isPendingIntentBalAllowedByPermission() + && hasBalPermission(state.mRealCallingUid, state.mRealCallingPid)) { + return new BalVerdict(BAL_ALLOW_PERMISSION, + /*background*/ false, + "realCallingUid has BAL permission."); + } + // if the realCallingUid is a persistent system process, abort if the IntentSender // wasn't allowed to start an activity if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts() @@ -1660,26 +1657,62 @@ public class BackgroundActivityStartController { (state.mOriginatingPendingIntent != null)); } - @BalCode int code = finalVerdict.getCode(); - int callingUid = state.mCallingUid; - int realCallingUid = state.mRealCallingUid; - Intent intent = state.mIntent; - - if (code == BAL_ALLOW_PENDING_INTENT - && (callingUid < Process.FIRST_APPLICATION_UID - || realCallingUid < Process.FIRST_APPLICATION_UID)) { - String activityName = intent != null - ? requireNonNull(intent.getComponent()).flattenToShortString() : ""; - writeBalAllowedLog(activityName, BAL_ALLOW_PENDING_INTENT, - state); + if (balImprovedMetrics()) { + if (shouldLogStats(finalVerdict, state)) { + String activityName; + if (shouldLogIntentActivity(finalVerdict, state)) { + Intent intent = state.mIntent; + activityName = intent == null ? "noIntent" // should never happen + : requireNonNull(intent.getComponent()).flattenToShortString(); + } else { + activityName = ""; + } + writeBalAllowedLog(activityName, finalVerdict.getCode(), state); + } + } else { + @BalCode int code = finalVerdict.getCode(); + int callingUid = state.mCallingUid; + int realCallingUid = state.mRealCallingUid; + Intent intent = state.mIntent; + + if (code == BAL_ALLOW_PENDING_INTENT + && (callingUid < Process.FIRST_APPLICATION_UID + || realCallingUid < Process.FIRST_APPLICATION_UID)) { + String activityName = intent != null + ? requireNonNull(intent.getComponent()).flattenToShortString() : ""; + writeBalAllowedLog(activityName, BAL_ALLOW_PENDING_INTENT, + state); + } + if (code == BAL_ALLOW_PERMISSION || code == BAL_ALLOW_FOREGROUND + || code == BAL_ALLOW_SAW_PERMISSION) { + // We don't need to know which activity in this case. + writeBalAllowedLog("", code, state); + } } - if (code == BAL_ALLOW_PERMISSION || code == BAL_ALLOW_FOREGROUND - || code == BAL_ALLOW_SAW_PERMISSION) { - // We don't need to know which activity in this case. - writeBalAllowedLog("", code, state); + return finalVerdict; + } + @VisibleForTesting + boolean shouldLogStats(BalVerdict finalVerdict, BalState state) { + if (finalVerdict.blocks()) { + return false; } - return finalVerdict; + if (!state.isPendingIntent() && finalVerdict.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) { + return false; + } + if (state.mBalAllowedByPiSender.allowsBackgroundActivityStarts() + && state.mResultForRealCaller != null + && state.mResultForRealCaller.getRawCode() == BAL_ALLOW_VISIBLE_WINDOW) { + return false; + } + return true; + } + + @VisibleForTesting + boolean shouldLogIntentActivity(BalVerdict finalVerdict, BalState state) { + return finalVerdict.mBasedOnRealCaller + ? state.mRealCallingUid < Process.FIRST_APPLICATION_UID + : state.mCallingUid < Process.FIRST_APPLICATION_UID; } @VisibleForTesting void writeBalAllowedLog(String activityName, int code, BalState state) { diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java index 1c599777e497..6aa00397fbf0 100644 --- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java +++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java @@ -80,8 +80,8 @@ public class DesktopModeLaunchParamsModifier implements LaunchParamsModifier { LaunchParamsController.LaunchParams outParams) { if (!canEnterDesktopMode(mContext)) { - appendLog("desktop mode is not enabled, continuing"); - return RESULT_CONTINUE; + appendLog("desktop mode is not enabled, skipping"); + return RESULT_SKIP; } if (task == null) { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2f37e8813365..e49cb386ac49 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -478,6 +478,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; @Nullable final CameraStateMonitor mCameraStateMonitor; + @Nullable + final ActivityRefresher mActivityRefresher; DisplayFrames mDisplayFrames; final DisplayUpdater mDisplayUpdater; @@ -550,15 +552,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 +564,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 +1182,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()); @@ -1258,13 +1235,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabledAtBuildTime(); if (shouldCreateDisplayRotationCompatPolicy) { mCameraStateMonitor = new CameraStateMonitor(this, mWmService.mH); + mActivityRefresher = new ActivityRefresher(mWmService, mWmService.mH); mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy( - this, mWmService.mH, mCameraStateMonitor); + this, mCameraStateMonitor, mActivityRefresher); mCameraStateMonitor.startListeningToCameraState(); } else { // These are to satisfy the `final` check. mCameraStateMonitor = null; + mActivityRefresher = null; mDisplayRotationCompatPolicy = null; } @@ -2738,6 +2717,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (!mVisibleBackgroundUserEnabled) { return true; } + if (isPrivate()) { + // UserManager doesn't track the user visibility for private displays. + return true; + } final int userId = UserHandle.getUserId(uid); return userId == UserHandle.USER_SYSTEM || mWmService.mUmInternal.isUserVisible(userId, mDisplayId); @@ -2776,7 +2759,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Nullable Task getTopRootTask() { - return getRootTask(t -> true); + return getRootTask(alwaysTruePredicate()); } /** @@ -3300,117 +3283,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); @@ -3767,7 +3639,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); @@ -4116,7 +3987,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } getInputMonitor().setFocusedAppLw(newFocus); - updateTouchExcludeRegion(); return true; } @@ -5143,7 +5013,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // This should be called after the insets have been dispatched to clients and we have // committed finish drawing windows. - mInsetsStateController.getImeSourceProvider().checkShowImePostLayout(); + mInsetsStateController.getImeSourceProvider().checkAndStartShowImePostLayout(); mLastHasContent = mTmpApplySurfaceChangesTransactionState.displayHasContent; if (!inTransition() && !mDisplayRotation.isRotatingSeamlessly()) { diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index eacf9a3fa759..e0cc064fcacc 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -18,8 +18,6 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; -import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; -import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; @@ -32,19 +30,14 @@ import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.view.Display.TYPE_INTERNAL; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION; -import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES; import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.StringRes; -import android.app.servertransaction.RefreshCallbackItem; -import android.app.servertransaction.ResumeActivityItem; import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.os.Handler; -import android.os.RemoteException; import android.widget.Toast; import com.android.internal.R; @@ -64,48 +57,38 @@ import com.android.server.UiThread; * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}. */ // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path -class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener { - - // Delay for updating display rotation after Camera connection is closed. Needed to avoid - // rotation flickering when an app is flipping between front and rear cameras or when size - // compat mode is restarted. - // TODO(b/263114289): Consider associating this delay with a specific activity so that if - // the new non-camera activity started on top of the camer one we can rotate faster. - private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000; - // Delay for updating display rotation after Camera connection is opened. This delay is - // selected to be long enough to avoid conflicts with transitions on the app's side. - // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app - // is flipping between front and rear cameras (in case requested orientation changes at - // runtime at the same time) or when size compat mode is restarted. - private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS = - CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2; - // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The - // client process may not always report the event back to the server, such as process is - // crashed or got killed. - private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000; +final class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStateListener, + ActivityRefresher.Evaluator { + @NonNull private final DisplayContent mDisplayContent; + @NonNull private final WindowManagerService mWmService; + @NonNull private final CameraStateMonitor mCameraStateMonitor; - private final Handler mHandler; + @NonNull + private final ActivityRefresher mActivityRefresher; @ScreenOrientation private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET; - DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler, - @NonNull CameraStateMonitor cameraStateMonitor) { + DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, + @NonNull CameraStateMonitor cameraStateMonitor, + @NonNull ActivityRefresher activityRefresher) { // This constructor is called from DisplayContent constructor. Don't use any fields in // DisplayContent here since they aren't guaranteed to be set. - mHandler = handler; mDisplayContent = displayContent; mWmService = displayContent.mWmService; mCameraStateMonitor = cameraStateMonitor; mCameraStateMonitor.addCameraStateListener(this); + mActivityRefresher = activityRefresher; + mActivityRefresher.addEvaluator(this); } /** Releases camera state listener. */ void dispose() { mCameraStateMonitor.removeCameraStateListener(this); + mActivityRefresher.removeEvaluator(this); } /** @@ -169,47 +152,6 @@ class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStat } /** - * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle. - * This allows to clear cached values in apps (e.g. display or camera rotation) that influence - * camera preview and can lead to sideways or stretching issues persisting even after force - * rotation. - */ - void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig, - Configuration lastReportedConfig) { - if (!isTreatmentEnabledForDisplay() - || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled() - || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) { - return; - } - boolean cycleThroughStop = - mWmService.mLetterboxConfiguration - .isCameraCompatRefreshCycleThroughStopEnabled() - && !activity.mLetterboxUiController - .shouldRefreshActivityViaPauseForCameraCompat(); - try { - activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true); - ProtoLog.v(WM_DEBUG_STATES, - "Refreshing activity for camera compatibility treatment, " - + "activityRecord=%s", activity); - final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain( - activity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); - final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain( - activity.token, /* isForward */ false, /* shouldSendCompatFakeFocus */ false); - activity.mAtmService.getLifecycleManager().scheduleTransactionAndLifecycleItems( - activity.app.getThread(), refreshCallbackItem, resumeActivityItem); - mHandler.postDelayed( - () -> onActivityRefreshed(activity), - REFRESH_CALLBACK_TIMEOUT_MS); - } catch (RemoteException e) { - activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false); - } - } - - void onActivityRefreshed(@NonNull ActivityRecord activity) { - activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false); - } - - /** * Notifies that animation in {@link ScreenRotationAnimation} has finished. * * <p>This class uses this signal as a trigger for notifying the user about forced rotation @@ -276,14 +218,16 @@ class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStat // Refreshing only when configuration changes after rotation or camera split screen aspect ratio // treatment is enabled - private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig, - Configuration lastReportedConfig) { + @Override + public boolean shouldRefreshActivity(@NonNull ActivityRecord activity, + @NonNull Configuration newConfig, @NonNull Configuration lastReportedConfig) { final boolean displayRotationChanged = (newConfig.windowConfiguration.getDisplayRotation() != lastReportedConfig.windowConfiguration.getDisplayRotation()); - return (displayRotationChanged - || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed()) + return isTreatmentEnabledForDisplay() && isTreatmentEnabledForActivity(activity) - && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat(); + && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat() + && (displayRotationChanged + || activity.mLetterboxUiController.isCameraCompatSplitScreenAspectRatioAllowed()); } /** @@ -310,7 +254,6 @@ class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStat && activity.mLetterboxUiController.shouldForceRotateForCameraCompat(); } - /** * Whether camera compat treatment is applicable for the given activity. * @@ -429,6 +372,6 @@ class DisplayRotationCompatPolicy implements CameraStateMonitor.CameraCompatStat || !mCameraStateMonitor.isCameraWithIdRunningForActivity(topActivity, cameraId)) { return false; } - return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested(); + return mActivityRefresher.isActivityRefreshing(topActivity); } } 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/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 092ff3dd07a5..e03ff6881bd8 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -24,7 +24,6 @@ import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; import static com.android.server.wm.ImeInsetsSourceProviderProto.IME_TARGET_FROM_IME; import static com.android.server.wm.ImeInsetsSourceProviderProto.INSETS_SOURCE_PROVIDER; -import static com.android.server.wm.ImeInsetsSourceProviderProto.IS_IME_LAYOUT_DRAWN; import static com.android.server.wm.WindowManagerService.H.UPDATE_MULTI_WINDOW_STACKS; import android.annotation.NonNull; @@ -52,19 +51,26 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { private static final String TAG = ImeInsetsSourceProvider.class.getSimpleName(); - /** The token tracking the current IME request or {@code null} otherwise. */ + /** The token tracking the show IME request, non-null only while a show request is pending. */ + @Nullable + private ImeTracker.Token mStatsToken; + /** The target that requested to show the IME, non-null only while a show request is pending. */ @Nullable - private ImeTracker.Token mImeRequesterStatsToken; private InsetsControlTarget mImeRequester; - private Runnable mShowImeRunner; - private boolean mIsImeLayoutDrawn; + /** @see #isImeShowing() */ private boolean mImeShowing; + /** The latest received insets source. */ private final InsetsSource mLastSource = new InsetsSource(ID_IME, WindowInsets.Type.ime()); /** @see #setFrozen(boolean) */ private boolean mFrozen; - /** @see #setServerVisible(boolean) */ + /** + * The server visibility of the source provider's window container. This is out of sync with + * {@link InsetsSourceProvider#mServerVisible} while {@link #mFrozen} is {@code true}. + * + * @see #setServerVisible + */ private boolean mServerVisible; ImeInsetsSourceProvider(@NonNull InsetsSource source, @@ -73,6 +79,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { super(source, stateController, displayContent); } + @Nullable @Override InsetsSourceControl getControl(InsetsControlTarget target) { final InsetsSourceControl control = super.getControl(target); @@ -124,9 +131,9 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { /** * Freeze IME insets source state when required. * - * When setting {@param frozen} as {@code true}, the IME insets provider will freeze the + * <p>When setting {@param frozen} as {@code true}, the IME insets provider will freeze the * current IME insets state and pending the IME insets state update until setting - * {@param frozen} as {@code false}. + * {@param frozen} as {@code false}.</p> */ void setFrozen(boolean frozen) { if (mFrozen == frozen) { @@ -223,27 +230,29 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { /** * Called from {@link WindowManagerInternal#showImePostLayout} * when {@link android.inputmethodservice.InputMethodService} requests to show IME - * on {@param imeTarget}. + * on the given control target. * - * @param imeTarget imeTarget on which IME request is coming from. + * @param imeTarget the control target on which the IME request is coming from. * @param statsToken the token tracking the current IME request. */ - void scheduleShowImePostLayout(InsetsControlTarget imeTarget, + void scheduleShowImePostLayout(@NonNull InsetsControlTarget imeTarget, @NonNull ImeTracker.Token statsToken) { - if (mImeRequesterStatsToken != null) { - // Cancel the pre-existing stats token, if any. - // Log state on pre-existing request cancel. - logShowImePostLayoutState(false /* aborted */); - ImeTracker.forLogging().onCancelled( - mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); - } - mImeRequesterStatsToken = statsToken; - boolean targetChanged = isTargetChangedWithinActivity(imeTarget); + if (mImeRequester == null) { + // Start tracing only on initial scheduled show IME request, to record end-to-end time. + Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); + } else { + // We already have a scheduled show IME request, cancel the previous statsToken and + // continue with the new one. + logIsScheduledAndReadyToShowIme(false /* aborted */); + ImeTracker.forLogging().onCancelled(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); + } + final boolean targetChanged = isTargetChangedWithinActivity(imeTarget); mImeRequester = imeTarget; + mStatsToken = statsToken; if (targetChanged) { // target changed, check if new target can show IME. ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord"); - checkShowImePostLayout(); + checkAndStartShowImePostLayout(); // if IME cannot be shown at this time, it is scheduled to be shown. // once window that called IMM.showSoftInput() and DisplayContent's ImeTarget match, // it will be shown. @@ -252,79 +261,58 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null ? mImeRequester : mImeRequester.getWindow().getName()); - mShowImeRunner = () -> { - ImeTracker.forLogging().onProgress(mImeRequesterStatsToken, - ImeTracker.PHASE_WM_SHOW_IME_RUNNER); - ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); - // Target should still be the same. - if (isReadyToShowIme()) { - ImeTracker.forLogging().onProgress(mImeRequesterStatsToken, - ImeTracker.PHASE_WM_SHOW_IME_READY); - final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); - - ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", - target.getWindow() != null ? target.getWindow().getName() : ""); - setImeShowing(true); - target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, - mImeRequesterStatsToken); - Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); - if (target != mImeRequester && mImeRequester != null) { - ProtoLog.w(WM_DEBUG_IME, - "showInsets(ime) was requested by different window: %s ", - (mImeRequester.getWindow() != null - ? mImeRequester.getWindow().getName() : "")); - } - } else { - ImeTracker.forLogging().onFailed(mImeRequesterStatsToken, - ImeTracker.PHASE_WM_SHOW_IME_READY); - } - // Clear token here so we don't report an error in abortShowImePostLayout(). - mImeRequesterStatsToken = null; - abortShowImePostLayout(); - }; mDisplayContent.mWmService.requestTraversal(); } - void checkShowImePostLayout() { - if (mWindowContainer == null) { + /** + * Checks whether there is a previously scheduled show IME request and we are ready to show, + * in which case also start handling the request. + */ + void checkAndStartShowImePostLayout() { + if (!isScheduledAndReadyToShowIme()) { + // This can later become ready, so we don't want to cancel the pending request here. return; } - WindowState windowState = mWindowContainer.asWindowState(); - if (windowState == null) { - throw new IllegalArgumentException("IME insets must be provided by a window."); - } - // check if IME is drawn - if (mIsImeLayoutDrawn - || (isReadyToShowIme() - && windowState.isDrawn() - && !windowState.mGivenInsetsPending)) { - mIsImeLayoutDrawn = true; - // show IME if InputMethodService requested it to be shown. - if (mShowImeRunner != null) { - mShowImeRunner.run(); - } + + ImeTracker.forLogging().onProgress(mStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); + ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); + + final InsetsControlTarget target = getControlTarget(); + + ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", + target.getWindow() != null ? target.getWindow().getName() : ""); + setImeShowing(true); + target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, mStatsToken); + Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); + if (target != mImeRequester) { + ProtoLog.w(WM_DEBUG_IME, "showInsets(ime) was requested by different window: %s ", + (mImeRequester.getWindow() != null ? mImeRequester.getWindow().getName() : "")); } + resetShowImePostLayout(); } - /** - * Abort any pending request to show IME post layout. - */ + /** Aborts the previously scheduled show IME request. */ void abortShowImePostLayout() { - ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout"); - if (mImeRequesterStatsToken != null) { - // Log state on abort. - logShowImePostLayoutState(true /* aborted */); - ImeTracker.forLogging().onFailed( - mImeRequesterStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT); - mImeRequesterStatsToken = null; + if (mImeRequester == null) { + return; } + ProtoLog.d(WM_DEBUG_IME, "abortShowImePostLayout"); + Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); + logIsScheduledAndReadyToShowIme(true /* aborted */); + ImeTracker.forLogging().onFailed( + mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT); + resetShowImePostLayout(); + } + + /** Resets the state of the previously scheduled show IME request. */ + private void resetShowImePostLayout() { mImeRequester = null; - mIsImeLayoutDrawn = false; - mShowImeRunner = null; + mStatsToken = null; } + /** Checks whether there is a previously scheduled show IME request and we are ready to show. */ @VisibleForTesting - boolean isReadyToShowIme() { + boolean isScheduledAndReadyToShowIme() { // IMMS#mLastImeTargetWindow always considers focused window as // IME target, however DisplayContent#computeImeTarget() can compute // a different IME target. @@ -334,32 +322,47 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // Also, if imeTarget is closing, it would be considered as outdated target. // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of // actual IME target. + if (mImeRequester == null) { + // No show IME request previously scheduled. + return false; + } + if (!mServerVisible || mFrozen) { + // The window container is not available and considered visible. + // If frozen, the server visibility is not set until unfrozen. + return false; + } + if (mWindowContainer == null) { + // No window container set. + return false; + } + final WindowState windowState = mWindowContainer.asWindowState(); + if (windowState == null) { + throw new IllegalArgumentException("IME insets must be provided by a window."); + } + if (!windowState.isDrawn() || windowState.mGivenInsetsPending) { + // The window is not drawn, or it has pending insets. + return false; + } final InsetsControlTarget dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); - if (dcTarget == null || mImeRequester == null) { - // Not ready to show if there is no IME layering target, or no IME requester. + if (dcTarget == null) { + // No IME layering target. return false; } final InsetsControlTarget controlTarget = getControlTarget(); if (controlTarget == null) { - // Not ready to show if there is no IME control target. + // No IME control target. return false; } if (controlTarget != mDisplayContent.getImeTarget(IME_TARGET_CONTROL)) { - // Not ready to show if control target does not match the one in DisplayContent. - return false; - } - if (!mServerVisible || mFrozen) { - // Not ready to show if the window container is not available and considered visible. - // If frozen, the server visibility is not set until unfrozen. + // The control target does not match the one in DisplayContent. return false; } if (mStateController.hasPendingControls(controlTarget)) { - // Not ready to show if control target has pending controls. + // The control target has pending controls. return false; } if (getLeash(controlTarget) == null) { - // Not ready to show if control target has no source control leash (or leash is not - // ready for dispatching). + // The control target has no source control leash (or it is not ready for dispatching). return false; } @@ -371,51 +374,44 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { || isAboveImeLayeringTarget(mImeRequester, dcTarget) || isImeFallbackTarget(mImeRequester) || isImeInputTarget(mImeRequester) - || sameAsImeControlTarget(); + || sameAsImeControlTarget(mImeRequester); } /** - * Logs the current state required for showImePostLayout to be triggered. + * Logs the current state that can be checked by {@link #isScheduledAndReadyToShowIme}. * - * @param aborted whether the showImePostLayout was aborted or cancelled. + * @param aborted whether the scheduled show IME request was aborted or cancelled. */ - private void logShowImePostLayoutState(boolean aborted) { + private void logIsScheduledAndReadyToShowIme(boolean aborted) { final var windowState = mWindowContainer != null ? mWindowContainer.asWindowState() : null; final var dcTarget = mDisplayContent.getImeTarget(IME_TARGET_LAYERING); final var controlTarget = getControlTarget(); final var sb = new StringBuilder(); sb.append("showImePostLayout ").append(aborted ? "aborted" : "cancelled"); - sb.append(", mWindowContainer is: "); - sb.append(mWindowContainer != null ? "non-null" : "null"); + sb.append(", isScheduledAndReadyToShowIme: ").append(isScheduledAndReadyToShowIme()); + sb.append(", mImeRequester: ").append(mImeRequester); + sb.append(", serverVisible: ").append(mServerVisible); + sb.append(", frozen: ").append(mFrozen); + sb.append(", mWindowContainer is: ").append(mWindowContainer != null ? "non-null" : "null"); sb.append(", windowState: ").append(windowState); if (windowState != null) { - sb.append(", windowState.isDrawn(): "); - sb.append(windowState.isDrawn()); - sb.append(", windowState.mGivenInsetsPending: "); - sb.append(windowState.mGivenInsetsPending); + sb.append(", isDrawn: ").append(windowState.isDrawn()); + sb.append(", mGivenInsetsPending: ").append(windowState.mGivenInsetsPending); } - sb.append(", mIsImeLayoutDrawn: ").append(mIsImeLayoutDrawn); - sb.append(", mShowImeRunner: ").append(mShowImeRunner); - sb.append(", mImeRequester: ").append(mImeRequester); sb.append(", dcTarget: ").append(dcTarget); sb.append(", controlTarget: ").append(controlTarget); - sb.append("\n"); - sb.append("isReadyToShowIme(): ").append(isReadyToShowIme()); if (mImeRequester != null && dcTarget != null && controlTarget != null) { - sb.append(", controlTarget == DisplayContent.controlTarget: "); + sb.append("\n"); + sb.append("controlTarget == DisplayContent.controlTarget: "); sb.append(controlTarget == mDisplayContent.getImeTarget(IME_TARGET_CONTROL)); sb.append(", hasPendingControls: "); sb.append(mStateController.hasPendingControls(controlTarget)); - sb.append(", serverVisible: "); - sb.append(mServerVisible); - sb.append(", frozen: "); - sb.append(mFrozen); - sb.append(", leash is: "); - sb.append(getLeash(controlTarget) != null ? "non-null" : "null"); - sb.append(", control is: "); - sb.append(mControl != null ? "non-null" : "null"); - sb.append(", mIsLeashReadyForDispatching: "); - sb.append(mIsLeashReadyForDispatching); + final boolean hasLeash = getLeash(controlTarget) != null; + sb.append(", leash is: ").append(hasLeash ? "non-null" : "null"); + if (!hasLeash) { + sb.append(", control is: ").append(mControl != null ? "non-null" : "null"); + sb.append(", mIsLeashReadyForDispatching: ").append(mIsLeashReadyForDispatching); + } sb.append(", isImeLayeringTarget: "); sb.append(isImeLayeringTarget(mImeRequester, dcTarget)); sb.append(", isAboveImeLayeringTarget: "); @@ -425,7 +421,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { sb.append(", isImeInputTarget: "); sb.append(isImeInputTarget(mImeRequester)); sb.append(", sameAsImeControlTarget: "); - sb.append(sameAsImeControlTarget()); + sb.append(sameAsImeControlTarget(mImeRequester)); } Slog.d(TAG, sb.toString()); } @@ -445,19 +441,18 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { && dcTarget.getWindow().mSubLayer > target.getWindow().mSubLayer; } - private boolean isImeFallbackTarget(InsetsControlTarget target) { + private boolean isImeFallbackTarget(@NonNull InsetsControlTarget target) { return target == mDisplayContent.getImeFallback(); } - private boolean isImeInputTarget(InsetsControlTarget target) { + private boolean isImeInputTarget(@NonNull InsetsControlTarget target) { return target == mDisplayContent.getImeInputTarget(); } - private boolean sameAsImeControlTarget() { - final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); - return target == mImeRequester - && (mImeRequester.getWindow() == null - || !isImeTargetWindowClosing(mImeRequester.getWindow())); + private boolean sameAsImeControlTarget(@NonNull InsetsControlTarget target) { + final InsetsControlTarget controlTarget = getControlTarget(); + return controlTarget == target + && (target.getWindow() == null || !isImeTargetWindowClosing(target.getWindow())); } private static boolean isImeTargetWindowClosing(@NonNull WindowState win) { @@ -467,16 +462,15 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { || win.mActivityRecord.willCloseOrEnterPip()); } - private boolean isTargetChangedWithinActivity(InsetsControlTarget target) { + private boolean isTargetChangedWithinActivity(@NonNull InsetsControlTarget target) { // We don't consider the target out of the activity. - if (target == null || target.getWindow() == null) { + if (target.getWindow() == null) { return false; } return mImeRequester != target - && mImeRequester != null && mShowImeRunner != null + && mImeRequester != null && mImeRequester.getWindow() != null - && mImeRequester.getWindow().mActivityRecord - == target.getWindow().mActivityRecord; + && mImeRequester.getWindow().mActivityRecord == target.getWindow().mActivityRecord; } // --------------------------------------------------------------------------------------- @@ -509,7 +503,6 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (imeRequesterWindow != null) { imeRequesterWindow.dumpDebug(proto, IME_TARGET_FROM_IME, logLevel); } - proto.write(IS_IME_LAYOUT_DRAWN, mIsImeLayoutDrawn); proto.end(token); } diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java index ef1b02d8accc..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 */), @@ -58,8 +56,9 @@ class InputConfigAdapter { LayoutParams.INPUT_FEATURE_SPY, InputConfig.SPY, false /* inverted */), new FlagMapping( - LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING, - InputConfig.SENSITIVE_FOR_TRACING, false /* inverted */)); + LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, + 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/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 88587666e321..2288fe998b58 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -198,7 +198,7 @@ class InsetsSourceProvider { if (mControllable) { mWindowContainer.setControllableInsetProvider(this); if (mPendingControlTarget != mControlTarget) { - updateControlForTarget(mPendingControlTarget, true /* force */); + mStateController.notifyControlTargetChanged(mPendingControlTarget, this); } } } @@ -351,42 +351,47 @@ class InsetsSourceProvider { ? windowState.wouldBeVisibleIfPolicyIgnored() && windowState.isVisibleByPolicy() : mWindowContainer.isVisibleRequested(); setServerVisible(isServerVisible); - if (mControl != null) { - boolean changed = false; - final Point position = getWindowFrameSurfacePosition(); - if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) { - changed = true; - if (windowState != null && windowState.getWindowFrames().didFrameSizeChange() - && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { - mHasPendingPosition = true; - windowState.applyWithNextDraw(mSetLeashPositionConsumer); - } else { - Transaction t = mWindowContainer.getSyncTransaction(); - if (windowState != null) { - // Make the buffer, token transformation, and leash position to be updated - // together when the window is drawn for new rotation. Otherwise the window - // may be outside the screen by the inconsistent orientations. - final AsyncRotationController rotationController = - mDisplayContent.getAsyncRotationController(); - if (rotationController != null) { - final Transaction drawT = - rotationController.getDrawTransaction(windowState.mToken); - if (drawT != null) { - t = drawT; - } + updateInsetsControlPosition(windowState); + } + + void updateInsetsControlPosition(WindowState windowState) { + if (mControl == null) { + return; + } + boolean changed = false; + final Point position = getWindowFrameSurfacePosition(); + if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) { + changed = true; + if (windowState != null && windowState.getWindowFrames().didFrameSizeChange() + && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { + mHasPendingPosition = true; + windowState.applyWithNextDraw(mSetLeashPositionConsumer); + } else { + Transaction t = mWindowContainer.getSyncTransaction(); + if (windowState != null) { + // Make the buffer, token transformation, and leash position to be updated + // together when the window is drawn for new rotation. Otherwise the window + // may be outside the screen by the inconsistent orientations. + final AsyncRotationController rotationController = + mDisplayContent.getAsyncRotationController(); + if (rotationController != null) { + final Transaction drawT = + rotationController.getDrawTransaction(windowState.mToken); + if (drawT != null) { + t = drawT; } } - mSetLeashPositionConsumer.accept(t); } + mSetLeashPositionConsumer.accept(t); } - final Insets insetsHint = getInsetsHint(); - if (!mControl.getInsetsHint().equals(insetsHint)) { - mControl.setInsetsHint(insetsHint); - changed = true; - } - if (changed) { - mStateController.notifyControlChanged(mControlTarget); - } + } + final Insets insetsHint = getInsetsHint(); + if (!mControl.getInsetsHint().equals(insetsHint)) { + mControl.setInsetsHint(insetsHint); + changed = true; + } + if (changed) { + mStateController.notifyControlChanged(mControlTarget); } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index dfee16440518..7a1f57bea680 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -389,7 +389,7 @@ class InsetsStateController { newControlTargets.clear(); // Check for and try to run the scheduled show IME request (if it exists), as we // now applied the surface transaction and notified the target of the new control. - getImeSourceProvider().checkShowImePostLayout(); + getImeSourceProvider().checkAndStartShowImePostLayout(); }); } diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 5aa0ed7ce76c..b5af8065726d 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -19,13 +19,13 @@ 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; import android.content.Context; import android.graphics.Color; import android.provider.DeviceConfig; -import android.util.Slog; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; @@ -33,6 +33,7 @@ import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.function.Function; +import java.util.function.IntSupplier; /** Reads letterbox configs from resources and controls their overrides at runtime. */ final class LetterboxConfiguration { @@ -265,6 +266,12 @@ final class LetterboxConfiguration { // unresizable apps private boolean mIsDisplayAspectRatioEnabledForFixedOrientationLetterbox; + // Supplier for the value in pixel to consider when detecting vertical thin letterboxing + private final DimenPxIntSupplier mThinLetterboxWidthPxSupplier; + + // Supplier for the value in pixel to consider when detecting horizontal thin letterboxing + private final DimenPxIntSupplier mThinLetterboxHeightPxSupplier; + // Allows to enable letterboxing strategy for translucent activities ignoring flags. private boolean mTranslucentLetterboxingOverrideEnabled; @@ -301,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( @@ -359,6 +394,11 @@ final class LetterboxConfiguration { mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled); + mThinLetterboxWidthPxSupplier = new DimenPxIntSupplier(mContext, + R.dimen.config_letterboxThinLetterboxWidthDp); + mThinLetterboxHeightPxSupplier = new DimenPxIntSupplier(mContext, + R.dimen.config_letterboxThinLetterboxHeightDp); + mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); @@ -1129,6 +1169,24 @@ final class LetterboxConfiguration { } /** + * @return Width in pixel about the padding to use to understand if the letterbox for an + * activity is thin. If the available space has width W and the app has width w, this + * is the maximum value for (W - w) / 2 to be considered for a thin letterboxed app. + */ + int getThinLetterboxWidthPx() { + return mThinLetterboxWidthPxSupplier.getAsInt(); + } + + /** + * @return Height in pixel about the padding to use to understand if a letterbox is thin. + * If the available space has height H and the app has height h, this is the maximum + * value for (H - h) / 2 to be considered for a thin letterboxed app. + */ + int getThinLetterboxHeightPx() { + return mThinLetterboxHeightPxSupplier.getAsInt(); + } + + /** * Overrides whether using split screen aspect ratio as a default aspect ratio for unresizable * apps. */ diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index b38e666ea691..6e11e082cc07 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -260,7 +260,7 @@ final class LetterboxUiController { // Whether activity "refresh" was requested but not finished in // ActivityRecord#activityResumedLocked following the camera compat force rotation in // DisplayRotationCompatPolicy. - private boolean mIsRefreshAfterRotationRequested; + private boolean mIsRefreshRequested; @NonNull private final OptProp mIgnoreRequestedOrientationOptProp; @@ -571,15 +571,14 @@ final class LetterboxUiController { } /** - * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked} - * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}. + * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}. */ - boolean isRefreshAfterRotationRequested() { - return mIsRefreshAfterRotationRequested; + boolean isRefreshRequested() { + return mIsRefreshRequested; } - void setIsRefreshAfterRotationRequested(boolean isRequested) { - mIsRefreshAfterRotationRequested = isRequested; + void setIsRefreshRequested(boolean isRequested) { + mIsRefreshRequested = isRequested; } boolean isOverrideRespectRequestedOrientationEnabled() { @@ -989,6 +988,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. @@ -1024,6 +1027,67 @@ final class LetterboxUiController { return getSplitScreenAspectRatio(); } + /** + * @return {@value true} if the resulting app is letterboxed in a way defined as thin. + */ + boolean isVerticalThinLetterboxed() { + final int thinHeight = mLetterboxConfiguration.getThinLetterboxHeightPx(); + if (thinHeight < 0) { + return false; + } + final Task task = mActivityRecord.getTask(); + if (task == null) { + return false; + } + final int padding = Math.abs( + task.getBounds().height() - mActivityRecord.getBounds().height()) / 2; + return padding <= thinHeight; + } + + /** + * @return {@value true} if the resulting app is pillarboxed in a way defined as thin. + */ + boolean isHorizontalThinLetterboxed() { + final int thinWidth = mLetterboxConfiguration.getThinLetterboxWidthPx(); + if (thinWidth < 0) { + return false; + } + final Task task = mActivityRecord.getTask(); + if (task == null) { + return false; + } + final int padding = Math.abs( + task.getBounds().width() - mActivityRecord.getBounds().width()) / 2; + return padding <= thinWidth; + } + + + /** + * @return {@value true} if the vertical reachability should be allowed in case of + * thin letteboxing + */ + boolean allowVerticalReachabilityForThinLetterbox() { + if (!Flags.disableThinLetterboxingPolicy()) { + return true; + } + // When the flag is enabled we allow vertical reachability only if the + // app is not thin letterboxed vertically. + return !isVerticalThinLetterboxed(); + } + + /** + * @return {@value true} if the vertical reachability should be enabled in case of + * thin letteboxing + */ + boolean allowHorizontalReachabilityForThinLetterbox() { + if (!Flags.disableThinLetterboxingPolicy()) { + return true; + } + // When the flag is enabled we allow horizontal reachability only if the + // app is not thin pillarboxed. + return !isHorizontalThinLetterboxed(); + } + float getSplitScreenAspectRatio() { // Getting the same aspect ratio that apps get in split screen. final DisplayArea displayArea = mActivityRecord.getDisplayArea(); @@ -1085,6 +1149,17 @@ final class LetterboxUiController { } boolean shouldApplyUserFullscreenOverride() { + // Do not override orientation to fullscreen for camera activities. + // Fixed-orientation activities are rarely tested in other orientations, and it often + // results in sideways or stretched previews. As the camera compat treatment targets + // fixed-orientation activities, overriding the orientation disables the treatment. + final DisplayContent displayContent = mActivityRecord.mDisplayContent; + if (displayContent != null && displayContent.mDisplayRotationCompatPolicy != null + && displayContent.mDisplayRotationCompatPolicy + .isCameraActive(mActivityRecord, /* mustBeFullscreen= */ true)) { + return false; + } + if (isUserFullscreenOverrideEnabled()) { mUserAspectRatio = getUserMinAspectRatioOverrideCode(); @@ -1263,6 +1338,9 @@ final class LetterboxUiController { * </ul> */ private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) { + if (!allowHorizontalReachabilityForThinLetterbox()) { + return false; + } // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior() @@ -1298,6 +1376,9 @@ final class LetterboxUiController { * </ul> */ private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) { + if (!allowVerticalReachabilityForThinLetterbox()) { + return false; + } // Use screen resolved bounds which uses resolved bounds or size compat bounds // as activity bounds can sometimes be empty final Rect opaqueActivityBounds = hasInheritedLetterboxBehavior() @@ -1566,6 +1647,8 @@ final class LetterboxUiController { if (!shouldShowLetterboxUi) { return; } + pw.println(prefix + " isVerticalThinLetterboxed=" + isVerticalThinLetterboxed()); + pw.println(prefix + " isHorizontalThinLetterboxed=" + isHorizontalThinLetterboxed()); pw.println(prefix + " letterboxBackgroundColor=" + Integer.toHexString( getLetterboxBackgroundColor().toArgb())); pw.println(prefix + " letterboxBackgroundType=" diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS index ce47f5cc8a7e..60454fc5d7c6 100644 --- a/services/core/java/com/android/server/wm/OWNERS +++ b/services/core/java/com/android/server/wm/OWNERS @@ -18,6 +18,7 @@ rgl@google.com yunfanc@google.com wilsonshih@google.com jiamingliu@google.com +pdwilliams@google.com # Files related to background activity launches per-file Background*Start* = set noparent diff --git a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java index 498182dab9c3..3606a34e23e0 100644 --- a/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java +++ b/services/core/java/com/android/server/wm/PerfettoTransitionTracer.java @@ -41,8 +41,12 @@ class PerfettoTransitionTracer implements TransitionTracer { PerfettoTransitionTracer() { Producer.init(InitArguments.DEFAULTS); - mDataSource.register( - new DataSourceParams(PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)); + DataSourceParams params = + new DataSourceParams.Builder() + .setBufferExhaustedPolicy( + PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT) + .build(); + mDataSource.register(params); } /** diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java index 8e78d25d8373..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; @@ -708,26 +707,6 @@ class RecentTasks { } } - /** - * Removes the oldest recent task that is compatible with the given one. This is possible if - * the task windowing mode changed after being added to the Recents. - */ - void removeCompatibleRecentTask(Task task) { - final int taskIndex = mTasks.indexOf(task); - if (taskIndex < 0) { - return; - } - - final int candidateIndex = findRemoveIndexForTask(task, false /* includingSelf */); - if (candidateIndex == -1) { - // Nothing to trim - return; - } - - final Task taskToRemove = taskIndex > candidateIndex ? task : mTasks.get(candidateIndex); - remove(taskToRemove); - } - void removeTasksByPackageName(String packageName, int userId) { for (int i = mTasks.size() - 1; i >= 0; --i) { final Task task = mTasks.get(i); @@ -1615,10 +1594,6 @@ class RecentTasks { * list (if any). */ private int findRemoveIndexForAddTask(Task task) { - return findRemoveIndexForTask(task, true /* includingSelf */); - } - - private int findRemoveIndexForTask(Task task, boolean includingSelf) { final int recentsCount = mTasks.size(); final Intent intent = task.intent; final boolean document = intent != null && intent.isDocument(); @@ -1674,8 +1649,6 @@ class RecentTasks { // existing task continue; } - } else if (!includingSelf) { - continue; } return i; } 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/SnapshotCache.java b/services/core/java/com/android/server/wm/SnapshotCache.java index 86804360f6f4..1e6ee7dc318f 100644 --- a/services/core/java/com/android/server/wm/SnapshotCache.java +++ b/services/core/java/com/android/server/wm/SnapshotCache.java @@ -92,6 +92,7 @@ abstract class SnapshotCache<TYPE extends WindowContainer> { if (entry != null) { mAppIdMap.remove(entry.topApp); mRunningCache.remove(id); + entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE); } } } diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java index 357897127f3a..16fcb097ca5c 100644 --- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java +++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java @@ -253,6 +253,7 @@ class SnapshotPersistQueue { PersistInfoProvider provider) { super(provider, userId); mId = id; + snapshot.addReference(TaskSnapshot.REFERENCE_PERSIST); mSnapshot = snapshot; } @@ -289,6 +290,7 @@ class SnapshotPersistQueue { if (failed) { deleteSnapshot(mId, mUserId, mPersistInfoProvider); } + mSnapshot.removeReference(TaskSnapshot.REFERENCE_PERSIST); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } @@ -346,6 +348,9 @@ class SnapshotPersistQueue { + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed."); return false; } + final int width = bitmap.getWidth(); + final int height = bitmap.getHeight(); + bitmap.recycle(); final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId); try { @@ -363,8 +368,8 @@ class SnapshotPersistQueue { } final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap, - (int) (bitmap.getWidth() * mPersistInfoProvider.lowResScaleFactor()), - (int) (bitmap.getHeight() * mPersistInfoProvider.lowResScaleFactor()), + (int) (width * mPersistInfoProvider.lowResScaleFactor()), + (int) (height * mPersistInfoProvider.lowResScaleFactor()), true /* filter */); swBitmap.recycle(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 56e5d76ac4e0..a555388ab233 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -20,7 +20,6 @@ import static android.app.ActivityManager.isStartResultSuccessful; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE; import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; -import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION; import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; @@ -171,7 +170,6 @@ import android.util.proto.ProtoOutputStream; import android.view.DisplayInfo; import android.view.InsetsState; import android.view.RemoteAnimationAdapter; -import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.WindowManager.TransitionOldType; @@ -299,6 +297,10 @@ class Task extends TaskFragment { ActivityRecord mTranslucentActivityWaiting = null; ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent = new ArrayList<>(); + // The topmost Activity that was converted to translucent for scene transition, which should + // be converted from translucent once the transition is completed, or the app died. + private ActivityRecord mPendingConvertFromTranslucentActivity = null; + /** * Set when we know we are going to be calling updateConfiguration() * soon, so want to skip intermediate config checks. @@ -362,6 +364,10 @@ class Task extends TaskFragment { * user wants to return to it. */ private WindowProcessController mRootProcess; + /** The TF host info are set once the task has ever added an organized task fragment. */ + int mTaskFragmentHostUid; + String mTaskFragmentHostProcessName; + /** Takes on same value as first root activity */ boolean isPersistable = false; int maxRecents; @@ -438,16 +444,6 @@ class Task extends TaskFragment { // Id of the previous display the root task was on. int mPrevDisplayId = INVALID_DISPLAY; - /** ID of the display which rotation {@link #mRotation} has. */ - private int mLastRotationDisplayId = INVALID_DISPLAY; - - /** - * Display rotation as of the last time {@link #setBounds(Rect)} was called or this task was - * moved to a new display. - */ - @Surface.Rotation - private int mRotation; - int mMultiWindowRestoreWindowingMode = INVALID_WINDOWING_MODE; /** @@ -458,10 +454,7 @@ class Task extends TaskFragment { */ int mLastReportedRequestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; - // For comparison with DisplayContent bounds. - private Rect mTmpRect = new Rect(); - // For handling display rotations. - private Rect mTmpRect2 = new Rect(); + private final Rect mTmpRect = new Rect(); // Resize mode of the task. See {@link ActivityInfo#resizeMode} // Based on the {@link ActivityInfo#resizeMode} of the root activity. @@ -1194,9 +1187,6 @@ class Task extends TaskFragment { updateOverrideConfigurationFromLaunchBounds(); } - // Update task bounds if needed. - adjustBoundsForDisplayChangeIfNeeded(getDisplayContent()); - mRootWindowContainer.updateUIDsPresentOnDisplay(); // Ensure all animations are finished at same time in split-screen mode. @@ -1468,6 +1458,11 @@ class Task extends TaskFragment { // passed from Task constructor. final TaskFragment childTaskFrag = child.asTaskFragment(); if (childTaskFrag != null && childTaskFrag.asTask() == null) { + if (childTaskFrag.mTaskFragmentOrganizerProcessName != null + && mTaskFragmentHostProcessName == null) { + mTaskFragmentHostUid = childTaskFrag.mTaskFragmentOrganizerUid; + mTaskFragmentHostProcessName = childTaskFrag.mTaskFragmentOrganizerProcessName; + } childTaskFrag.setMinDimensions(mMinWidth, mMinHeight); // The starting window should keep covering its task when a pure TaskFragment is added @@ -2731,15 +2726,7 @@ class Task extends TaskFragment { return setBounds(getRequestedOverrideBounds(), bounds); } - int rotation = Surface.ROTATION_0; - final DisplayContent displayContent = getRootTask() != null - ? getRootTask().getDisplayContent() : null; - if (displayContent != null) { - rotation = displayContent.getDisplayInfo().rotation; - } - final int boundsChange = super.setBounds(bounds); - mRotation = rotation; updateSurfacePositionNonOrganized(); return boundsChange; } @@ -2799,10 +2786,6 @@ class Task extends TaskFragment { @Override void onDisplayChanged(DisplayContent dc) { - final boolean isRootTask = isRootTask(); - if (!isRootTask && !mCreatedByOrganizer) { - adjustBoundsForDisplayChangeIfNeeded(dc); - } super.onDisplayChanged(dc); if (isLeafTask()) { final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY; @@ -2953,48 +2936,6 @@ class Task extends TaskFragment { return mDragResizing; } - void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) { - if (displayContent == null) { - return; - } - if (getRequestedOverrideBounds().isEmpty()) { - return; - } - final int displayId = displayContent.getDisplayId(); - final int newRotation = displayContent.getDisplayInfo().rotation; - if (displayId != mLastRotationDisplayId) { - // This task is on a display that it wasn't on. There is no point to keep the relative - // position if display rotations for old and new displays are different. Just keep these - // values. - mLastRotationDisplayId = displayId; - mRotation = newRotation; - return; - } - - if (mRotation == newRotation) { - // Rotation didn't change. We don't need to adjust the bounds to keep the relative - // position. - return; - } - - // Device rotation changed. - // - We don't want the task to move around on the screen when this happens, so update the - // task bounds so it stays in the same place. - // - Rotate the bounds and notify activity manager if the task can be resized independently - // from its root task. The root task will take care of task rotation for the other case. - mTmpRect2.set(getBounds()); - - if (!getWindowConfiguration().canResizeTask()) { - setBounds(mTmpRect2); - return; - } - - displayContent.rotateBounds(mRotation, newRotation, mTmpRect2); - if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) { - mAtmService.resizeTask(mTaskId, getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION); - } - } - /** Cancels any running app transitions associated with the task. */ void cancelTaskWindowTransition() { for (int i = mChildren.size() - 1; i >= 0; --i) { @@ -3176,6 +3117,11 @@ class Task extends TaskFragment { mTaskId, snapshot); } + void onSnapshotInvalidated() { + mAtmService.getTaskChangeNotificationController().notifyTaskSnapshotInvalidated(mTaskId); + } + + TaskDescription getTaskDescription() { return mTaskDescription; } @@ -3506,6 +3452,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() @@ -3520,8 +3468,6 @@ class Task extends TaskFragment { info.isVisibleRequested = isVisibleRequested(); info.isSleeping = shouldSleepActivities(); info.isTopActivityTransparent = top != null && !top.fillsParent(); - appCompatTaskInfo.isLetterboxDoubleTapEnabled = top != null - && top.mLetterboxUiController.isLetterboxDoubleTapEducationEnabled(); appCompatTaskInfo.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET; appCompatTaskInfo.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; appCompatTaskInfo.topActivityLetterboxWidth = TaskInfo.PROPERTY_VALUE_UNSET; @@ -3536,15 +3482,29 @@ class Task extends TaskFragment { appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width(); appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height(); } + // We need to consider if letterboxed or pillarboxed + // TODO(b/336807329) Encapsulate reachability logic + appCompatTaskInfo.isLetterboxDoubleTapEnabled = top != null + && top.mLetterboxUiController.isLetterboxDoubleTapEducationEnabled(); if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) { if (appCompatTaskInfo.isTopActivityPillarboxed()) { - // Pillarboxed - appCompatTaskInfo.topActivityLetterboxHorizontalPosition = - top.mLetterboxUiController.getLetterboxPositionForHorizontalReachability(); + if (top.mLetterboxUiController.allowHorizontalReachabilityForThinLetterbox()) { + // Pillarboxed + appCompatTaskInfo.topActivityLetterboxHorizontalPosition = + top.mLetterboxUiController + .getLetterboxPositionForHorizontalReachability(); + } else { + appCompatTaskInfo.isLetterboxDoubleTapEnabled = false; + } } else { - // Letterboxed - appCompatTaskInfo.topActivityLetterboxVerticalPosition = - top.mLetterboxUiController.getLetterboxPositionForVerticalReachability(); + if (top.mLetterboxUiController.allowVerticalReachabilityForThinLetterbox()) { + // Letterboxed + appCompatTaskInfo.topActivityLetterboxVerticalPosition = + top.mLetterboxUiController + .getLetterboxPositionForVerticalReachability(); + } else { + appCompatTaskInfo.isLetterboxDoubleTapEnabled = false; + } } } appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton = top != null @@ -5032,6 +4992,27 @@ class Task extends TaskFragment { } } + void abortTranslucentActivityWaiting(@NonNull ActivityRecord r) { + if (r != mTranslucentActivityWaiting && r != mPendingConvertFromTranslucentActivity) { + return; + } + + if (mTranslucentActivityWaiting != null) { + if (!mTranslucentActivityWaiting.finishing) { + mTranslucentActivityWaiting.setOccludesParent(true); + } + mTranslucentActivityWaiting = null; + } + if (mPendingConvertFromTranslucentActivity != null) { + if (!mPendingConvertFromTranslucentActivity.finishing) { + mPendingConvertFromTranslucentActivity.setOccludesParent(true); + } + mPendingConvertFromTranslucentActivity = null; + } + mUndrawnActivitiesBelowTopTranslucent.clear(); + mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG); + } + void checkTranslucentActivityWaiting(ActivityRecord top) { if (mTranslucentActivityWaiting != top) { mUndrawnActivitiesBelowTopTranslucent.clear(); @@ -5046,10 +5027,19 @@ class Task extends TaskFragment { void convertActivityToTranslucent(ActivityRecord r) { mTranslucentActivityWaiting = r; + mPendingConvertFromTranslucentActivity = r; mUndrawnActivitiesBelowTopTranslucent.clear(); mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT); } + void convertActivityFromTranslucent(ActivityRecord r) { + if (r != mPendingConvertFromTranslucentActivity) { + Slog.e(TAG, "convertFromTranslucent expects " + mPendingConvertFromTranslucentActivity + + " but is " + r); + } + mPendingConvertFromTranslucentActivity = null; + } + /** * Called as activities below the top translucent activity are redrawn. When the last one is * redrawn notify the top activity by calling @@ -6883,7 +6873,7 @@ class Task extends TaskFragment { mIsBoosted = isBoosted; // The client transaction will be applied together with the next assignLayer. if (clientTransaction != null) { - mDecorSurfaceContainer.mPendingClientTransactions.add(clientTransaction); + mPendingClientTransactions.add(clientTransaction); } } diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java index 9324e29daafb..586f3c35c0c4 100644 --- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java +++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java @@ -61,6 +61,7 @@ class TaskChangeNotificationController { private static final int NOTIFY_ACTIVITY_ROTATED_MSG = 26; private static final int NOTIFY_TASK_MOVED_TO_BACK_LISTENERS_MSG = 27; private static final int NOTIFY_LOCK_TASK_MODE_CHANGED_MSG = 28; + private static final int NOTIFY_TASK_SNAPSHOT_INVALIDATED_LISTENERS_MSG = 29; // Delay in notifying task stack change listeners (in millis) private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100; @@ -150,6 +151,9 @@ class TaskChangeNotificationController { private final TaskStackConsumer mNotifyTaskSnapshotChanged = (l, m) -> { l.onTaskSnapshotChanged(m.arg1, (TaskSnapshot) m.obj); }; + private final TaskStackConsumer mNotifyTaskSnapshotInvalidated = (l, m) -> { + l.onTaskSnapshotInvalidated(m.arg1); + }; private final TaskStackConsumer mNotifyTaskDisplayChanged = (l, m) -> { l.onTaskDisplayChanged(m.arg1, m.arg2); @@ -243,6 +247,7 @@ class TaskChangeNotificationController { break; case NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG: forAllRemoteListeners(mNotifyTaskSnapshotChanged, msg); + ((TaskSnapshot) msg.obj).removeReference(TaskSnapshot.REFERENCE_BROADCAST); break; case NOTIFY_BACK_PRESSED_ON_TASK_ROOT: forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg); @@ -271,6 +276,9 @@ class TaskChangeNotificationController { case NOTIFY_LOCK_TASK_MODE_CHANGED_MSG: forAllRemoteListeners(mNotifyLockTaskModeChanged, msg); break; + case NOTIFY_TASK_SNAPSHOT_INVALIDATED_LISTENERS_MSG: + forAllRemoteListeners(mNotifyTaskSnapshotInvalidated, msg); + break; } if (msg.obj instanceof SomeArgs) { ((SomeArgs) msg.obj).recycle(); @@ -478,6 +486,7 @@ class TaskChangeNotificationController { * Notify listeners that the snapshot of a task has changed. */ void notifyTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { + snapshot.addReference(TaskSnapshot.REFERENCE_BROADCAST); final Message msg = mHandler.obtainMessage(NOTIFY_TASK_SNAPSHOT_CHANGED_LISTENERS_MSG, taskId, 0, snapshot); forAllLocalListeners(mNotifyTaskSnapshotChanged, msg); @@ -485,6 +494,16 @@ class TaskChangeNotificationController { } /** + * Notify listeners that the snapshot of a task is invalidated. + */ + void notifyTaskSnapshotInvalidated(int taskId) { + final Message msg = mHandler.obtainMessage(NOTIFY_TASK_SNAPSHOT_INVALIDATED_LISTENERS_MSG, + taskId, 0 /* unused */); + forAllLocalListeners(mNotifyTaskSnapshotInvalidated, msg); + msg.sendToTarget(); + } + + /** * Notify listeners that an activity received a back press when there are no other activities * in the back stack. */ diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 2c27b98250c9..eff831552320 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -223,7 +223,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { @VisibleForTesting Task getTopRootTask() { - return getRootTask(t -> true); + return getRootTask(alwaysTruePredicate()); } @Nullable diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 2b631f7a404e..e03458462a06 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -320,9 +320,9 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Organizer that organizing this TaskFragment. */ @Nullable private ITaskFragmentOrganizer mTaskFragmentOrganizer; - @VisibleForTesting + int mTaskFragmentOrganizerUid = INVALID_UID; - private @Nullable String mTaskFragmentOrganizerProcessName; + @Nullable String mTaskFragmentOrganizerProcessName; /** Client assigned unique token for this TaskFragment if this is created by an organizer. */ @Nullable @@ -354,14 +354,21 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** * Whether the activity navigation should be isolated. That is, Activities cannot be launched - * on an isolated TaskFragment, unless the activity is launched from an Activity in the same - * isolated TaskFragment, or explicitly requested to be launched to. - * <p> - * Note that only an embedded TaskFragment can be isolated. + * on an isolated TaskFragment unless explicitly requested to be launched to. */ private boolean mIsolatedNav; /** + * Whether the TaskFragment to be pinned. + * <p> + * If a TaskFragment is pinned, the TaskFragment should be the top-most TaskFragment among other + * sibling TaskFragments. Any newly launched and embeddable activity should not be placed in the + * pinned TaskFragment, unless the activity is launched from the pinned TaskFragment or + * explicitly requested to. Non-embeddable activities are not restricted to. + */ + private boolean mPinned; + + /** * Whether the TaskFragment should move to bottom of task when any activity below it is * launched in clear top mode. */ @@ -485,14 +492,16 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ @Nullable private WindowProcessController getOrganizerProcessIfDifferent(@Nullable ActivityRecord r) { - if ((r == null || mTaskFragmentOrganizerProcessName == null) - || (mTaskFragmentOrganizerProcessName.equals(r.processName) - && mTaskFragmentOrganizerUid == r.getUid())) { - // No organizer or the process is the same. + final Task task = getTask(); + if (r == null || task == null || task.mTaskFragmentHostProcessName == null) { return null; } - return mAtmService.getProcessController(mTaskFragmentOrganizerProcessName, - mTaskFragmentOrganizerUid); + if (task.mTaskFragmentHostProcessName.equals(r.processName) + && task.mTaskFragmentHostUid == r.getUid()) { + return null; + } + return mAtmService.getProcessController(task.mTaskFragmentHostProcessName, + task.mTaskFragmentHostUid); } void setAnimationParams(@NonNull TaskFragmentAnimationParams animationParams) { @@ -513,6 +522,18 @@ class TaskFragment extends WindowContainer<WindowContainer> { } /** + * Sets whether this TaskFragment {@link #isPinned()}. + * <p> + * Note that this is no-op if the TaskFragment is not {@link #isEmbedded() embedded}. + */ + void setPinned(boolean pinned) { + if (!isEmbedded()) { + return; + } + mPinned = pinned; + } + + /** * Sets whether transitions are allowed when the TaskFragment is empty. If {@code true}, * transitions are allowed when the TaskFragment is empty. If {@code false}, transitions * will wait until the TaskFragment becomes non-empty or other conditions are met. Default @@ -530,6 +551,15 @@ class TaskFragment extends WindowContainer<WindowContainer> { return isEmbedded() && mIsolatedNav; } + /** + * Indicates whether this TaskFragment is pinned. + * + * @see android.window.TaskFragmentOperation#OP_TYPE_SET_PINNED + */ + boolean isPinned() { + return isEmbedded() && mPinned; + } + TaskFragment getAdjacentTaskFragment() { return mAdjacentTaskFragment; } @@ -562,7 +592,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { } void setResumedActivity(ActivityRecord r, String reason) { - warnForNonLeafTaskFragment("setResumedActivity"); if (mResumedActivity == r) { return; } @@ -848,15 +877,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { return parentTaskFragment != null ? parentTaskFragment.getOrganizedTaskFragment() : null; } - /** - * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}. - */ - private void warnForNonLeafTaskFragment(String func) { - if (!isLeafTaskFragment()) { - Slog.w(TAG, func + " on non-leaf task fragment " + this); - } - } - boolean hasDirectChildActivities() { for (int i = mChildren.size() - 1; i >= 0; --i) { if (mChildren.get(i).asActivityRecord() != null) { @@ -933,7 +953,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { */ void onActivityStateChanged(ActivityRecord record, ActivityRecord.State state, String reason) { - warnForNonLeafTaskFragment("onActivityStateChanged"); if (record == mResumedActivity && state != RESUMED) { setResumedActivity(null, reason + " - onActivityStateChanged"); } @@ -963,7 +982,6 @@ class TaskFragment extends WindowContainer<WindowContainer> { * @return {@code true} if the process of the pausing activity is died. */ boolean handleAppDied(WindowProcessController app) { - warnForNonLeafTaskFragment("handleAppDied"); boolean isPausingDied = false; if (mPausingActivity != null && mPausingActivity.app == app) { ProtoLog.v(WM_DEBUG_STATES, "App died while pausing: %s", @@ -1221,7 +1239,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // have any running activities, not starting one and not home stack. shouldBeVisible = hasRunningActivities || (starting != null && starting.isDescendantOf(this)) - || isActivityTypeHome(); + || (isActivityTypeHome() && !isEmbedded()); break; } @@ -2893,6 +2911,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { return !mCreatedByOrganizer || mIsRemovalRequested; } + /** + * Returns whether this TaskFragment is going to be removed. + */ + boolean isRemovalRequested() { + return mIsRemovalRequested; + } + @Override void removeChild(WindowContainer child) { removeChild(child, true /* removeSelfIfPossible */); diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index 24b533a23af6..c4e932abecd3 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -365,7 +365,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr @Nullable TaskFragmentTransaction.Change prepareActivityReparentedToTask( - @NonNull ActivityRecord activity) { + @NonNull ActivityRecord activity, @Nullable ActivityRecord nextFillTaskActivity, + @Nullable IBinder lastParentTfToken) { if (activity.finishing) { Slog.d(TAG, "Reparent activity=" + activity.token + " is finishing"); return null; @@ -408,10 +409,21 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Activity=%s reparent to taskId=%d", activity.token, task.mTaskId); - return new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK) - .setTaskId(task.mTaskId) - .setActivityIntent(trimIntent(activity.intent)) - .setActivityToken(activityToken); + + final TaskFragmentTransaction.Change change = + new TaskFragmentTransaction.Change(TYPE_ACTIVITY_REPARENTED_TO_TASK) + .setTaskId(task.mTaskId) + .setActivityIntent(trimIntent(activity.intent)) + .setActivityToken(activityToken); + if (lastParentTfToken != null) { + change.setTaskFragmentToken(lastParentTfToken); + } + // Only pass the activity token to the client if it belongs to the same process. + if (Flags.fixPipRestoreToOverlay() && nextFillTaskActivity != null + && nextFillTaskActivity.getPid() == mOrganizerPid) { + change.setOtherActivityToken(nextFillTaskActivity.token); + } + return change; } void dispatchTransaction(@NonNull TaskFragmentTransaction transaction) { @@ -733,13 +745,13 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } void onActivityReparentedToTask(@NonNull ActivityRecord activity) { + final Task task = activity.getTask(); final ITaskFragmentOrganizer organizer; if (activity.mLastTaskFragmentOrganizerBeforePip != null) { // If the activity is previously embedded in an organized TaskFragment. organizer = activity.mLastTaskFragmentOrganizerBeforePip; } else { // Find the topmost TaskFragmentOrganizer. - final Task task = activity.getTask(); final TaskFragment[] organizedTf = new TaskFragment[1]; task.forAllLeafTaskFragments(tf -> { if (tf.isOrganizedTaskFragment()) { @@ -757,10 +769,24 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr Slog.w(TAG, "The last TaskFragmentOrganizer no longer exists"); return; } - addPendingEvent(new PendingTaskFragmentEvent.Builder( + + final IBinder parentTfTokenBeforePip = activity.getLastEmbeddedParentTfTokenBeforePip(); + final PendingTaskFragmentEvent.Builder builder = new PendingTaskFragmentEvent.Builder( PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK, organizer) .setActivity(activity) - .build()); + .setTaskFragmentToken(activity.getLastEmbeddedParentTfTokenBeforePip()); + + // Sets the next activity behinds the reparented Activity that's also not in the last + // embedded parent TF. + final ActivityRecord candidateAssociatedActivity = task.getActivity( + ar -> ar != activity && !ar.finishing + && ar.getTaskFragment().getFragmentToken() != parentTfTokenBeforePip); + if (candidateAssociatedActivity != null && (!candidateAssociatedActivity.isEmbedded() + || candidateAssociatedActivity.getTaskFragment().fillsParent())) { + builder.setOtherActivity(candidateAssociatedActivity); + } + + addPendingEvent(builder.build()); } void onTaskFragmentParentInfoChanged(@NonNull ITaskFragmentOrganizer organizer, @@ -889,11 +915,16 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr @Nullable private final TaskFragment mTaskFragment; @Nullable + private final IBinder mTaskFragmentToken; + @Nullable private final IBinder mErrorCallbackToken; @Nullable private final Throwable mException; @Nullable private final ActivityRecord mActivity; + // An additional Activity that's needed to send back to the client other than the mActivity. + @Nullable + private final ActivityRecord mOtherActivity; @Nullable private final Task mTask; // Set when the event is deferred due to the host task is invisible. The defer time will @@ -905,17 +936,21 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private PendingTaskFragmentEvent(@EventType int eventType, ITaskFragmentOrganizer taskFragmentOrg, @Nullable TaskFragment taskFragment, + @Nullable IBinder taskFragmentToken, @Nullable IBinder errorCallbackToken, @Nullable Throwable exception, @Nullable ActivityRecord activity, + @Nullable ActivityRecord otherActivity, @Nullable Task task, @TaskFragmentOperation.OperationType int opType) { mEventType = eventType; mTaskFragmentOrg = taskFragmentOrg; mTaskFragment = taskFragment; + mTaskFragmentToken = taskFragmentToken; mErrorCallbackToken = errorCallbackToken; mException = exception; mActivity = activity; + mOtherActivity = otherActivity; mTask = task; mOpType = opType; } @@ -943,12 +978,16 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr @Nullable private TaskFragment mTaskFragment; @Nullable + private IBinder mTaskFragmentToken; + @Nullable private IBinder mErrorCallbackToken; @Nullable private Throwable mException; @Nullable private ActivityRecord mActivity; @Nullable + private ActivityRecord mOtherActivity; + @Nullable private Task mTask; @TaskFragmentOperation.OperationType private int mOpType; @@ -963,6 +1002,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return this; } + Builder setTaskFragmentToken(@Nullable IBinder fragmentToken) { + mTaskFragmentToken = fragmentToken; + return this; + } + Builder setErrorCallbackToken(@Nullable IBinder errorCallbackToken) { mErrorCallbackToken = errorCallbackToken; return this; @@ -978,6 +1022,11 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return this; } + Builder setOtherActivity(@NonNull ActivityRecord otherActivity) { + mOtherActivity = otherActivity; + return this; + } + Builder setTask(@NonNull Task task) { mTask = requireNonNull(task); return this; @@ -990,7 +1039,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr PendingTaskFragmentEvent build() { return new PendingTaskFragmentEvent(mEventType, mTaskFragmentOrg, mTaskFragment, - mErrorCallbackToken, mException, mActivity, mTask, mOpType); + mTaskFragmentToken, mErrorCallbackToken, mException, mActivity, + mOtherActivity, mTask, mOpType); } } } @@ -1191,7 +1241,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr return state.prepareTaskFragmentError(event.mErrorCallbackToken, taskFragment, event.mOpType, event.mException); case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENTED_TO_TASK: - return state.prepareActivityReparentedToTask(event.mActivity); + return state.prepareActivityReparentedToTask(event.mActivity, event.mOtherActivity, + event.mTaskFragmentToken); default: throw new IllegalArgumentException("Unknown TaskFragmentEvent=" + event.mEventType); } diff --git a/services/core/java/com/android/server/wm/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java index b69ac1bb2795..64b9df59990b 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java @@ -35,9 +35,11 @@ class TaskSnapshotCache extends SnapshotCache<Task> { void putSnapshot(Task task, TaskSnapshot snapshot) { synchronized (mLock) { + snapshot.addReference(TaskSnapshot.REFERENCE_CACHE); final CacheEntry entry = mRunningCache.get(task.mTaskId); if (entry != null) { mAppIdMap.remove(entry.topApp); + entry.snapshot.removeReference(TaskSnapshot.REFERENCE_CACHE); } final ActivityRecord top = task.getTopMostActivity(); mAppIdMap.put(top, task.mTaskId); 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/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index ce53290da49c..2dc439da992d 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -492,6 +492,27 @@ class TransitionController { return false; } + /** Returns {@code true} if the display contains a transient-launch transition. */ + boolean hasTransientLaunch(@NonNull DisplayContent dc) { + if (mCollectingTransition != null && mCollectingTransition.hasTransientLaunch() + && mCollectingTransition.isOnDisplay(dc)) { + return true; + } + for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) { + final Transition transition = mWaitingTransitions.get(i); + if (transition.hasTransientLaunch() && transition.isOnDisplay(dc)) { + return true; + } + } + for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) { + final Transition transition = mPlayingTransitions.get(i); + if (transition.hasTransientLaunch() && transition.isOnDisplay(dc)) { + return true; + } + } + return false; + } + boolean isTransientHide(@NonNull Task task) { if (mCollectingTransition != null && mCollectingTransition.isInTransientHide(task)) { return true; diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 65e17615f775..3e43f5a2da66 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -165,7 +165,7 @@ class WallpaperController { || (w.mActivityRecord != null && !w.mActivityRecord.fillsParent()); } } else if (w.hasWallpaper() && mService.mPolicy.isKeyguardHostWindow(w.mAttrs) - && w.mTransitionController.isTransitionOnDisplay(mDisplayContent)) { + && w.mTransitionController.hasTransientLaunch(mDisplayContent)) { // If we have no candidates at all, notification shade is allowed to be the target // of last resort even if it has not been made visible yet. if (DEBUG_WALLPAPER) Slog.v(TAG, "Found keyguard as wallpaper target: " + w); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index d70ca02cc23d..edbba9244738 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -116,6 +116,7 @@ import com.android.internal.util.ToBooleanFunction; import com.android.server.wm.SurfaceAnimator.Animatable; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; +import com.android.server.wm.utils.AlwaysTruePredicate; import java.io.PrintWriter; import java.lang.ref.WeakReference; @@ -2019,29 +2020,34 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< callback, boundary, includeBoundary, traverseTopToBottom, boundaryFound); } + @SuppressWarnings("unchecked") + static <T> Predicate<T> alwaysTruePredicate() { + return (Predicate<T>) AlwaysTruePredicate.INSTANCE; + } + ActivityRecord getActivityAbove(ActivityRecord r) { - return getActivity((above) -> true, r, + return getActivity(alwaysTruePredicate(), r /* boundary */, false /*includeBoundary*/, false /*traverseTopToBottom*/); } ActivityRecord getActivityBelow(ActivityRecord r) { - return getActivity((below) -> true, r, + return getActivity(alwaysTruePredicate(), r /* boundary */, false /*includeBoundary*/, true /*traverseTopToBottom*/); } ActivityRecord getBottomMostActivity() { - return getActivity((r) -> true, false /*traverseTopToBottom*/); + return getActivity(alwaysTruePredicate(), false /* traverseTopToBottom */); } ActivityRecord getTopMostActivity() { - return getActivity((r) -> true, true /*traverseTopToBottom*/); + return getActivity(alwaysTruePredicate(), true /* traverseTopToBottom */); } ActivityRecord getTopActivity(boolean includeFinishing, boolean includeOverlays) { // Break down into 4 calls to avoid object creation due to capturing input params. if (includeFinishing) { if (includeOverlays) { - return getActivity((r) -> true); + return getActivity(alwaysTruePredicate()); } return getActivity((r) -> !r.isTaskOverlay()); } else if (includeOverlays) { @@ -2220,21 +2226,17 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } } - Task getTaskAbove(Task t) { - return getTask( - (above) -> true, t, false /*includeBoundary*/, false /*traverseTopToBottom*/); - } - Task getTaskBelow(Task t) { - return getTask((below) -> true, t, false /*includeBoundary*/, true /*traverseTopToBottom*/); + return getTask(alwaysTruePredicate(), t /* boundary */, + false /* includeBoundary */, true /* traverseTopToBottom */); } Task getBottomMostTask() { - return getTask((t) -> true, false /*traverseTopToBottom*/); + return getTask(alwaysTruePredicate(), false /* traverseTopToBottom */); } Task getTopMostTask() { - return getTask((t) -> true, true /*traverseTopToBottom*/); + return getTask(alwaysTruePredicate(), true /* traverseTopToBottom */); } Task getTask(Predicate<Task> callback) { diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index eeedec398be4..19053f70e7e6 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -306,6 +306,18 @@ public abstract class WindowManagerInternal { } /** + * An interface to be notified on window removal. + */ + public interface OnWindowRemovedListener { + /** + * Called when a window is removed. + * + * @param token the client token + */ + void onWindowRemoved(IBinder token); + } + + /** * An interface to be notified when keyguard exit animation should start. */ public interface KeyguardExitAnimationStartListener { @@ -1076,6 +1088,20 @@ public abstract class WindowManagerInternal { public abstract void clearBlockedApps(); /** + * Register a listener to receive a callback on window removal. + * + * @param listener the listener to be registered. + */ + public abstract void registerOnWindowRemovedListener(OnWindowRemovedListener listener); + + /** + * Removes the listener. + * + * @param listener the listener to be removed. + */ + public abstract void unregisterOnWindowRemovedListener(OnWindowRemovedListener listener); + + /** * Moves the current focus to the adjacent activity if it has the latest created window. */ public abstract boolean moveFocusToAdjacentEmbeddedActivityIfNeeded(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index feede01bdf41..b6035519fdba 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -44,6 +44,7 @@ import static android.os.Process.myUid; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.permission.flags.Flags.sensitiveContentImprovements; import static android.permission.flags.Flags.sensitiveContentMetricsBugfix; +import static android.permission.flags.Flags.sensitiveContentRecentsScreenshotBugfix; import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT; import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; @@ -53,6 +54,7 @@ import static android.service.dreams.Flags.dreamHandlesConfirmKeys; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.flags.Flags.sensitiveContentAppProtection; import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; @@ -67,7 +69,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL; -import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; @@ -79,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; @@ -147,6 +145,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.SHOW_STACK_CRAWLS; import static com.android.server.wm.WindowManagerDebugConfig.SHOW_VERBOSE_TRANSACTIONS; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; +import static com.android.server.wm.WindowManagerInternal.OnWindowRemovedListener; import static com.android.server.wm.WindowManagerServiceDumpProto.BACK_NAVIGATION; import static com.android.server.wm.WindowManagerServiceDumpProto.DISPLAY_FROZEN; import static com.android.server.wm.WindowManagerServiceDumpProto.FOCUSED_APP; @@ -200,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; @@ -286,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; @@ -332,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; @@ -489,6 +486,9 @@ public class WindowManagerService extends IWindowManager.Stub private final RemoteCallbackList<IKeyguardLockedStateListener> mKeyguardLockedStateListeners = new RemoteCallbackList<>(); + + private final List<OnWindowRemovedListener> mOnWindowRemovedListeners = new ArrayList<>(); + private boolean mDispatchedKeyguardLockedState = false; // VR Vr2d Display Id. @@ -534,6 +534,24 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, + boolean asProto) { + 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, + timeoutMs + ); + dumpVisibleWindowClients(fd, pw, timeoutMs); + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { doDump(fd, pw, args, asProto); } @@ -1502,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, @@ -1812,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(); @@ -2073,7 +2075,11 @@ public class WindowManagerService extends IWindowManager.Stub */ void postWindowRemoveCleanupLocked(WindowState win) { ProtoLog.v(WM_DEBUG_ADD_REMOVE, "postWindowRemoveCleanupLocked: %s", win); - mWindowMap.remove(win.mClient.asBinder()); + final IBinder client = win.mClient.asBinder(); + mWindowMap.remove(client); + if (sensitiveContentAppProtection()) { + notifyWindowRemovedListeners(client); + } final DisplayContent dc = win.getDisplayContent(); dc.getDisplayRotation().markForSeamlessRotation(win, false /* seamlesslyRotated */); @@ -5335,6 +5341,23 @@ public class WindowManagerService extends IWindowManager.Stub } } + private void notifyWindowRemovedListeners(IBinder client) { + OnWindowRemovedListener[] windowRemovedListeners; + synchronized (mGlobalLock) { + if (mOnWindowRemovedListeners.isEmpty()) { + return; + } + windowRemovedListeners = new OnWindowRemovedListener[mOnWindowRemovedListeners.size()]; + mOnWindowRemovedListeners.toArray(windowRemovedListeners); + } + mH.post(() -> { + int size = windowRemovedListeners.length; + for (int i = 0; i < size; i++) { + windowRemovedListeners[i].onWindowRemoved(client); + } + }); + } + private void notifyWindowsChanged() { WindowChangeListener[] windowChangeListeners; synchronized (mGlobalLock) { @@ -5674,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; @@ -5907,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; @@ -7532,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: @@ -8424,7 +8302,6 @@ public class WindowManagerService extends IWindowManager.Stub ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET); - Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget(); imeTarget = controlTarget.getWindow(); // If InsetsControlTarget doesn't have a window, it's using remoteControlTarget @@ -8827,6 +8704,14 @@ public class WindowManagerService extends IWindowManager.Stub mRoot.forAllWindows((w) -> { if (w.isVisible()) { WindowManagerService.this.showToastIfBlockingScreenCapture(w); + } else if (sensitiveContentRecentsScreenshotBugfix() + && shouldInvalidateSnapshot(w)) { + final Task task = w.getTask(); + // preventing from showing up in starting window. + mTaskSnapshotController.removeAndDeleteSnapshot( + task.mTaskId, task.mUserId); + // Refresh TaskThumbnailCache + task.onSnapshotInvalidated(); } }, /* traverseTopToBottom= */ true); } @@ -8834,6 +8719,12 @@ public class WindowManagerService extends IWindowManager.Stub } } + private boolean shouldInvalidateSnapshot(WindowState w) { + return w.getTask() != null + && mSensitiveContentPackages.shouldBlockScreenCaptureForApp( + w.getOwningPackage(), w.getOwningUid(), w.getWindowToken()); + } + @Override public void removeBlockScreenCaptureForApps(ArraySet<PackageInfo> packageInfos) { synchronized (mGlobalLock) { @@ -8868,6 +8759,20 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public void registerOnWindowRemovedListener(OnWindowRemovedListener listener) { + synchronized (mGlobalLock) { + mOnWindowRemovedListeners.add(listener); + } + } + + @Override + public void unregisterOnWindowRemovedListener(OnWindowRemovedListener listener) { + synchronized (mGlobalLock) { + mOnWindowRemovedListeners.remove(listener); + } + } + + @Override public boolean moveFocusToAdjacentEmbeddedActivityIfNeeded() { synchronized (mGlobalLock) { final WindowState focusedWindow = getFocusedWindow(); @@ -9241,11 +9146,11 @@ public class WindowManagerService extends IWindowManager.Stub } } - // You can only use INPUT_FEATURE_SENSITIVE_FOR_TRACING on a trusted overlay. - if ((inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_TRACING) != 0 && !isTrustedOverlay) { - Slog.w(TAG, "Removing INPUT_FEATURE_SENSITIVE_FOR_TRACING from '" + windowName + // You can only use INPUT_FEATURE_SENSITIVE_FOR_PRIVACY on a trusted overlay. + if ((inputFeatures & INPUT_FEATURE_SENSITIVE_FOR_PRIVACY) != 0 && !isTrustedOverlay) { + Slog.w(TAG, "Removing INPUT_FEATURE_SENSITIVE_FOR_PRIVACY from '" + windowName + "' because it isn't a trusted overlay"); - return inputFeatures & ~INPUT_FEATURE_SENSITIVE_FOR_TRACING; + return inputFeatures & ~INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; } return inputFeatures; } @@ -10280,4 +10185,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/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 1573d09364ea..99c47360418b 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -39,6 +39,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOO import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK; import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION; import static android.window.TaskFragmentOperation.OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH; +import static android.window.TaskFragmentOperation.OP_TYPE_SET_PINNED; import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS; import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT; import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN; @@ -1627,6 +1628,11 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } break; } + case OP_TYPE_SET_PINNED: { + final boolean pinned = operation.getBooleanValue(); + taskFragment.setPinned(pinned); + break; + } } return effects; } @@ -1916,7 +1922,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub final int count = tasksToReparent.size(); for (int i = 0; i < count; ++i) { final Task task = tasksToReparent.get(i); - final int prevWindowingMode = task.getWindowingMode(); if (syncId >= 0) { addToSyncSet(syncId, task); } @@ -1930,12 +1935,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM, false /*moveParents*/, "processChildrenTaskReparentHierarchyOp"); } - // Trim the compatible Recent task (if any) after the Task is reparented and now has - // a different windowing mode, in order to prevent redundant Recent tasks after - // reparenting. - if (prevWindowingMode != task.getWindowingMode()) { - mService.mTaskSupervisor.mRecentTasks.removeCompatibleRecentTask(task); - } } if (transition != null) transition.collect(newParent); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index c25080f9e756..8fb83fa0e88c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -858,6 +858,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * {@link InsetsStateController#notifyInsetsChanged}. */ boolean isReadyToDispatchInsetsState() { + if (mStartingData != null) { + // Starting window doesn't consider insets. + return false; + } final boolean visible = shouldCheckTokenVisibleRequested() ? isVisibleRequested() : isVisible(); return visible && mFrozenInsetsState == null; @@ -1443,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(), @@ -2355,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(); @@ -5522,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 */); } @@ -6070,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/java/com/android/server/wm/utils/AlwaysTruePredicate.java b/services/core/java/com/android/server/wm/utils/AlwaysTruePredicate.java new file mode 100644 index 000000000000..49dcb6cab633 --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/AlwaysTruePredicate.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.utils; + +import java.util.function.Predicate; + +/** A simple Predicate to avoid synthetic allocation of lambda expression "o -> true". */ +public class AlwaysTruePredicate implements Predicate<Object> { + + public static final AlwaysTruePredicate INSTANCE = new AlwaysTruePredicate(); + + private AlwaysTruePredicate() { + } + + @Override + public boolean test(Object o) { + return true; + } +} diff --git a/services/core/jni/com_android_server_display_DisplayControl.cpp b/services/core/jni/com_android_server_display_DisplayControl.cpp index e65b9030195a..6613a250bd71 100644 --- a/services/core/jni/com_android_server_display_DisplayControl.cpp +++ b/services/core/jni/com_android_server_display_DisplayControl.cpp @@ -23,18 +23,22 @@ namespace android { -static jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj, jboolean secure, - jfloat requestedRefreshRate) { - ScopedUtfChars name(env, nameObj); - sp<IBinder> token(SurfaceComposerClient::createDisplay(String8(name.c_str()), bool(secure), - requestedRefreshRate)); +static jobject nativeCreateVirtualDisplay(JNIEnv* env, jclass clazz, jstring nameObj, + jboolean secure, jstring uniqueIdStr, + jfloat requestedRefreshRate) { + const ScopedUtfChars name(env, nameObj); + const ScopedUtfChars uniqueId(env, uniqueIdStr); + sp<IBinder> token(SurfaceComposerClient::createVirtualDisplay(std::string(name.c_str()), + bool(secure), + std::string(uniqueId.c_str()), + requestedRefreshRate)); return javaObjectForIBinder(env, token); } -static void nativeDestroyDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) { +static void nativeDestroyVirtualDisplay(JNIEnv* env, jclass clazz, jobject tokenObj) { sp<IBinder> token(ibinderForJavaObject(env, tokenObj)); if (token == NULL) return; - SurfaceComposerClient::destroyDisplay(token); + SurfaceComposerClient::destroyVirtualDisplay(token); } static void nativeOverrideHdrTypes(JNIEnv* env, jclass clazz, jobject tokenObject, @@ -178,10 +182,10 @@ static jobject nativeGetPhysicalDisplayToken(JNIEnv* env, jclass clazz, jlong ph static const JNINativeMethod sDisplayMethods[] = { // clang-format off - {"nativeCreateDisplay", "(Ljava/lang/String;ZF)Landroid/os/IBinder;", - (void*)nativeCreateDisplay }, - {"nativeDestroyDisplay", "(Landroid/os/IBinder;)V", - (void*)nativeDestroyDisplay }, + {"nativeCreateVirtualDisplay", "(Ljava/lang/String;ZLjava/lang/String;F)Landroid/os/IBinder;", + (void*)nativeCreateVirtualDisplay }, + {"nativeDestroyVirtualDisplay", "(Landroid/os/IBinder;)V", + (void*)nativeDestroyVirtualDisplay }, {"nativeOverrideHdrTypes", "(Landroid/os/IBinder;[I)V", (void*)nativeOverrideHdrTypes }, {"nativeGetPhysicalDisplayIds", "()[J", diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp index be188358b90b..2307aced6141 100644 --- a/services/core/jni/com_android_server_hint_HintManagerService.cpp +++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp @@ -34,11 +34,8 @@ #include "jni.h" -using aidl::android::hardware::power::SessionConfig; -using aidl::android::hardware::power::SessionHint; -using aidl::android::hardware::power::SessionMode; -using aidl::android::hardware::power::SessionTag; -using aidl::android::hardware::power::WorkDuration; +namespace hal = aidl::android::hardware::power; + using android::power::PowerHintSessionWrapper; namespace android { @@ -95,10 +92,11 @@ static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid, static jlong createHintSessionWithConfig(JNIEnv* env, int32_t tgid, int32_t uid, std::vector<int32_t> threadIds, int64_t durationNanos, - int32_t sessionTag, SessionConfig& config) { + int32_t sessionTag, hal::SessionConfig& config) { auto result = gPowerHalController.createHintSessionWithConfig(tgid, uid, threadIds, durationNanos, - static_cast<SessionTag>(sessionTag), + static_cast<hal::SessionTag>( + sessionTag), &config); if (result.isOk()) { jlong session_ptr = reinterpret_cast<jlong>(result.value().get()); @@ -140,12 +138,12 @@ static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDuration } static void reportActualWorkDuration(int64_t session_ptr, - const std::vector<WorkDuration>& actualDurations) { + const std::vector<hal::WorkDuration>& actualDurations) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->reportActualWorkDuration(actualDurations); } -static void sendHint(int64_t session_ptr, SessionHint hint) { +static void sendHint(int64_t session_ptr, hal::SessionHint hint) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->sendHint(hint); } @@ -155,7 +153,7 @@ static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadId appSession->setThreads(threadIds); } -static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) { +static void setMode(int64_t session_ptr, hal::SessionMode mode, bool enabled) { auto appSession = reinterpret_cast<PowerHintSessionWrapper*>(session_ptr); appSession->setMode(mode, enabled); } @@ -189,7 +187,7 @@ static jlong nativeCreateHintSessionWithConfig(JNIEnv* env, jclass /* clazz */, return 0; } std::vector<int32_t> threadIds(tidArray.get(), tidArray.get() + tidArray.size()); - SessionConfig config; + hal::SessionConfig config; jlong out = createHintSessionWithConfig(env, tgid, uid, std::move(threadIds), durationNanos, sessionTag, config); if (out <= 0) { @@ -223,7 +221,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon ScopedLongArrayRO arrayActualDurations(env, actualDurations); ScopedLongArrayRO arrayTimeStamps(env, timeStamps); - std::vector<WorkDuration> actualList(arrayActualDurations.size()); + std::vector<hal::WorkDuration> actualList(arrayActualDurations.size()); for (size_t i = 0; i < arrayActualDurations.size(); i++) { actualList[i].timeStampNanos = arrayTimeStamps[i]; actualList[i].durationNanos = arrayActualDurations[i]; @@ -232,7 +230,7 @@ static void nativeReportActualWorkDuration(JNIEnv* env, jclass /* clazz */, jlon } static void nativeSendHint(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint hint) { - sendHint(session_ptr, static_cast<SessionHint>(hint)); + sendHint(session_ptr, static_cast<hal::SessionHint>(hint)); } static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jintArray tids) { @@ -244,13 +242,13 @@ static void nativeSetThreads(JNIEnv* env, jclass /* clazz */, jlong session_ptr, static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint mode, jboolean enabled) { - setMode(session_ptr, static_cast<SessionMode>(mode), enabled); + setMode(session_ptr, static_cast<hal::SessionMode>(mode), enabled); } static void nativeReportActualWorkDuration2(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jobjectArray jWorkDurations) { int size = env->GetArrayLength(jWorkDurations); - std::vector<WorkDuration> workDurations(size); + std::vector<hal::WorkDuration> workDurations(size); for (int i = 0; i < size; i++) { jobject workDuration = env->GetObjectArrayElement(jWorkDurations, i); workDurations[i].workPeriodStartTimestampNanos = diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 80bf5b9d3fc0..b19de189af5a 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -88,7 +88,6 @@ namespace input_flags = com::android::input::flags; namespace android { -static const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer(); static const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl(); // The exponent used to calculate the pointer speed scaling factor. @@ -123,12 +122,11 @@ static struct { jmethodID interceptMotionBeforeQueueingNonInteractive; jmethodID interceptKeyBeforeDispatching; jmethodID dispatchUnhandledKey; - jmethodID onPointerDisplayIdChanged; jmethodID onPointerDownOutsideFocus; jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; jmethodID getInputPortAssociations; - jmethodID getInputUniqueIdAssociations; + jmethodID getInputUniqueIdAssociationsByPort; jmethodID getInputUniqueIdAssociationsByDescriptor; jmethodID getDeviceTypeAssociations; jmethodID getKeyboardLayoutAssociations; @@ -178,6 +176,7 @@ static struct { jfieldID lightTypeInput; jfieldID lightTypePlayerId; jfieldID lightTypeKeyboardBacklight; + jfieldID lightTypeKeyboardMicMute; jfieldID lightCapabilityBrightness; jfieldID lightCapabilityColorRgb; } gLightClassInfo; @@ -272,22 +271,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); @@ -297,25 +297,22 @@ public: void setShowTouches(bool enabled); void setInteractive(bool interactive); void reloadCalibration(); - void setPointerIconType(PointerIconStyle iconId); void reloadPointerIcons(); void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled); - void setCustomPointerIcon(const SpriteIcon& icon); 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); /* --- InputReaderPolicyInterface implementation --- */ void getReaderConfiguration(InputReaderConfiguration* outConfig) override; - std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override; void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override; std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay( const InputDeviceIdentifier& identifier, @@ -328,7 +325,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 --- */ @@ -351,13 +348,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; @@ -366,20 +365,22 @@ 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(); - virtual void onPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position); /* --- 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, @@ -403,24 +404,17 @@ 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}; - // Show touches feature enable/disable. - bool showTouches{false}; - // The latest request to enable or disable Pointer Capture. PointerCaptureRequest pointerCaptureRequest{}; // Sprite controller singleton, created on first use. std::shared_ptr<SpriteController> spriteController{}; - // TODO(b/293587049): Remove when the PointerChoreographer refactoring is complete. - // Pointer controller singleton, created and destroyed as needed. - std::weak_ptr<PointerController> legacyPointerController{}; - // The list of PointerControllers created and managed by the PointerChoreographer. std::list<std::weak_ptr<PointerController>> pointerControllers{}; @@ -428,7 +422,7 @@ private: std::set<int32_t> disabledInputDevices{}; // Associated Pointer controller display. - int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT}; + ui::LogicalDisplayId pointerDisplayId{ui::LogicalDisplayId::DEFAULT}; // True if stylus button reporting through motion events is enabled. bool stylusButtonMotionEventsEnabled{true}; @@ -461,7 +455,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( @@ -470,7 +464,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(); } }; @@ -501,16 +495,14 @@ 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 "Show Touches: %s\n", toString(mLocked.showTouches)); + toString(mLocked.pointerGesturesEnabled)); dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n", mLocked.pointerCaptureRequest.isEnable() ? "Enabled" : "Disabled", mLocked.pointerCaptureRequest.seq); - if (auto pc = mLocked.legacyPointerController.lock(); pc) { - dump += pc->dump(); - } } // release lock dump += "\n"; @@ -555,9 +547,7 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO [&viewports](PointerController& pc) { pc.onDisplayViewportsUpdated(viewports); }); } // release lock - if (ENABLE_POINTER_CHOREOGRAPHER) { - mInputManager->getChoreographer().setDisplayViewports(viewports); - } + mInputManager->getChoreographer().setDisplayViewports(viewports); mInputManager->getReader().requestRefreshConfiguration( InputReaderConfiguration::Change::DISPLAY_INFO); } @@ -569,7 +559,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); } @@ -611,7 +601,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon // Original data: [{'inputPort1': '1'}, {'inputPort2': '2'}] // Received data: ['inputPort1', '1', 'inputPort2', '2'] // So we unpack accordingly here. - outConfig->portAssociations.clear(); + outConfig->inputPortToDisplayPortAssociations.clear(); jobjectArray portAssociations = jobjectArray(env->CallObjectMethod(mServiceObj, gServiceClassInfo.getInputPortAssociations)); if (!checkAndClearExceptionFromCallback(env, "getInputPortAssociations") && portAssociations) { @@ -628,17 +618,16 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon displayPortStr.c_str()); continue; } - outConfig->portAssociations.insert({inputPort, displayPort}); + outConfig->inputPortToDisplayPortAssociations.insert({inputPort, displayPort}); } env->DeleteLocalRef(portAssociations); } - outConfig->uniqueIdAssociationsByPort = - readMapFromInterleavedJavaArray<std::string>(gServiceClassInfo - .getInputUniqueIdAssociations, - "getInputUniqueIdAssociations"); + outConfig->inputPortToDisplayUniqueIdAssociations = readMapFromInterleavedJavaArray< + std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByPort, + "getInputUniqueIdAssociationsByPort"); - outConfig->uniqueIdAssociationsByDescriptor = readMapFromInterleavedJavaArray< + outConfig->inputDeviceDescriptorToDisplayUniqueIdAssociations = readMapFromInterleavedJavaArray< std::string>(gServiceClassInfo.getInputUniqueIdAssociationsByDescriptor, "getInputUniqueIdAssociationsByDescriptor"); @@ -699,8 +688,6 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon : 1; outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled; - outConfig->showTouches = mLocked.showTouches; - outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest; outConfig->setDisplayViewports(mLocked.viewports); @@ -742,10 +729,6 @@ std::unordered_map<std::string, T> NativeInputManager::readMapFromInterleavedJav void NativeInputManager::forEachPointerControllerLocked( std::function<void(PointerController&)> apply) { - if (auto pc = mLocked.legacyPointerController.lock(); pc) { - apply(*pc); - } - auto it = mLocked.pointerControllers.begin(); while (it != mLocked.pointerControllers.end()) { auto pc = it->lock(); @@ -758,7 +741,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"; @@ -779,51 +762,17 @@ PointerIcon NativeInputManager::loadPointerIcon(JNIEnv* env, int32_t displayId, return android_view_PointerIcon_toNative(env, pointerIconObj.get()); } -// TODO(b/293587049): Remove the old way of obtaining PointerController when the -// PointerChoreographer refactoring is complete. -std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController( - int32_t /* deviceId */) { - ATRACE_CALL(); - std::scoped_lock _l(mLock); - - std::shared_ptr<PointerController> controller = mLocked.legacyPointerController.lock(); - if (controller == nullptr) { - ensureSpriteControllerLocked(); - - // Disable the functionality of the legacy PointerController if PointerChoreographer is - // enabled. - controller = PointerController::create(this, mLooper, *mLocked.spriteController, - /*enabled=*/!ENABLE_POINTER_CHOREOGRAPHER); - mLocked.legacyPointerController = controller; - updateInactivityTimeoutLocked(); - } - - return controller; -} - std::shared_ptr<PointerControllerInterface> NativeInputManager::createPointerController( PointerControllerInterface::ControllerType type) { std::scoped_lock _l(mLock); ensureSpriteControllerLocked(); std::shared_ptr<PointerController> pc = - PointerController::create(this, mLooper, *mLocked.spriteController, /*enabled=*/true, - type); + PointerController::create(this, mLooper, *mLocked.spriteController, type); mLocked.pointerControllers.emplace_back(pc); return pc; } -void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, - const FloatPoint& position) { - if (ENABLE_POINTER_CHOREOGRAPHER) { - return; - } - JNIEnv* env = jniEnv(); - env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId, - position.x, position.y); - checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged"); -} - -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 @@ -832,16 +781,10 @@ 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); - - // Notify the system. - JNIEnv* env = jniEnv(); - env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId, - position.x, position.y); - checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged"); } void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState, @@ -852,7 +795,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, @@ -874,9 +817,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); @@ -1078,8 +1022,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"); } } @@ -1165,12 +1108,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; } @@ -1180,7 +1123,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); } @@ -1208,24 +1151,8 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { }); } -void NativeInputManager::setPointerDisplayId(int32_t displayId) { - if (ENABLE_POINTER_CHOREOGRAPHER) { - mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId); - } else { - { // acquire lock - std::scoped_lock _l(mLock); - - if (mLocked.pointerDisplayId == displayId) { - return; - } - - ALOGI("Setting pointer display id to %d.", displayId); - mLocked.pointerDisplayId = displayId; - } // release lock - - mInputManager->getReader().requestRefreshConfiguration( - InputReaderConfiguration::Change::DISPLAY_INFO); - } +void NativeInputManager::setPointerDisplayId(ui::LogicalDisplayId displayId) { + mInputManager->getChoreographer().setDefaultMouseDisplayId(displayId); } int32_t NativeInputManager::getMousePointerSpeed() { @@ -1249,7 +1176,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); @@ -1259,8 +1187,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 { @@ -1377,24 +1305,7 @@ void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) } void NativeInputManager::setShowTouches(bool enabled) { - if (ENABLE_POINTER_CHOREOGRAPHER) { - mInputManager->getChoreographer().setShowTouchesEnabled(enabled); - return; - } - - { // acquire lock - std::scoped_lock _l(mLock); - - if (mLocked.showTouches == enabled) { - return; - } - - ALOGI("Setting show touches feature to %s.", enabled ? "enabled" : "disabled"); - mLocked.showTouches = enabled; - } // release lock - - mInputManager->getReader().requestRefreshConfiguration( - InputReaderConfiguration::Change::SHOW_TOUCHES); + mInputManager->getChoreographer().setShowTouchesEnabled(enabled); } void NativeInputManager::requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) { @@ -1410,30 +1321,15 @@ void NativeInputManager::reloadCalibration() { InputReaderConfiguration::Change::TOUCH_AFFINE_TRANSFORMATION); } -void NativeInputManager::setPointerIconType(PointerIconStyle iconId) { - std::scoped_lock _l(mLock); - std::shared_ptr<PointerController> controller = mLocked.legacyPointerController.lock(); - if (controller != nullptr) { - controller->updatePointerIcon(iconId); - } -} - void NativeInputManager::reloadPointerIcons() { std::scoped_lock _l(mLock); forEachPointerControllerLocked([](PointerController& pc) { pc.reloadPointerResources(); }); } -void NativeInputManager::setCustomPointerIcon(const SpriteIcon& icon) { - std::scoped_lock _l(mLock); - std::shared_ptr<PointerController> controller = mLocked.legacyPointerController.lock(); - if (controller != nullptr) { - controller->setCustomPointerIcon(icon); - } -} - 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 @@ -1445,10 +1341,7 @@ bool NativeInputManager::setPointerIcon( return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId); } -void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) { - if (!ENABLE_POINTER_CHOREOGRAPHER) { - return; - } +void NativeInputManager::setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) { mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible); } @@ -1503,7 +1396,7 @@ bool NativeInputManager::isInputMethodConnectionActive() { } std::optional<DisplayViewport> NativeInputManager::getPointerViewportForAssociatedDisplay( - int32_t associatedDisplayId) { + ui::LogicalDisplayId associatedDisplayId) { return mInputManager->getChoreographer().getViewportForPointerDevice(associatedDisplayId); } @@ -1578,9 +1471,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. @@ -1699,7 +1592,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); } @@ -1730,13 +1624,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(); @@ -1750,7 +1645,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(); @@ -1817,37 +1713,13 @@ void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) { InputReaderConfiguration::Change::STYLUS_BUTTON_REPORTING); } -FloatPoint NativeInputManager::getMouseCursorPosition(int32_t displayId) { - if (ENABLE_POINTER_CHOREOGRAPHER) { - return mInputManager->getChoreographer().getMouseCursorPosition(displayId); - } - // To maintain the status-quo, the displayId parameter (used when PointerChoreographer is - // enabled) is ignored in the old pipeline. - std::scoped_lock _l(mLock); - const auto pc = mLocked.legacyPointerController.lock(); - if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}; - - return pc->getPosition(); +FloatPoint NativeInputManager::getMouseCursorPosition(ui::LogicalDisplayId displayId) { + return mInputManager->getChoreographer().getMouseCursorPosition(displayId); } void NativeInputManager::setStylusPointerIconEnabled(bool enabled) { - if (ENABLE_POINTER_CHOREOGRAPHER) { - mInputManager->getChoreographer().setStylusPointerIconEnabled(enabled); - return; - } - - { // acquire lock - std::scoped_lock _l(mLock); - - if (mLocked.stylusPointerIconEnabled == enabled) { - return; - } - - mLocked.stylusPointerIconEnabled = enabled; - } // release lock - - mInputManager->getReader().requestRefreshConfiguration( - InputReaderConfiguration::Change::DISPLAY_INFO); + mInputManager->getChoreographer().setStylusPointerIconEnabled(enabled); + return; } void NativeInputManager::setInputMethodConnectionIsActive(bool isActive) { @@ -2007,7 +1879,7 @@ static jobject nativeCreateInputMonitor(JNIEnv* env, jobject nativeImplObj, jint jstring nameObj, jint pid) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - if (displayId == ADISPLAY_ID_NONE) { + if (ui::LogicalDisplayId{displayId} == ui::LogicalDisplayId::INVALID) { std::string message = "InputChannel used as a monitor must be associated with a display"; jniThrowRuntimeException(env, message.c_str()); return nullptr; @@ -2017,7 +1889,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(); @@ -2064,7 +1936,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, @@ -2156,20 +2029,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, @@ -2225,8 +2098,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; @@ -2249,7 +2122,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) { @@ -2432,6 +2305,9 @@ static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId } else if (lightInfo.type == InputDeviceLightType::KEYBOARD_BACKLIGHT) { jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeKeyboardBacklight); + } else if (lightInfo.type == InputDeviceLightType::KEYBOARD_MIC_MUTE) { + jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, + gLightClassInfo.lightTypeKeyboardMicMute); } else { ALOGW("Unknown light type %d", lightInfo.type); continue; @@ -2575,12 +2451,6 @@ static void nativeMonitor(JNIEnv* env, jobject nativeImplObj) { im->getInputManager()->getDispatcher().monitor(); } -static jboolean nativeIsInputDeviceEnabled(JNIEnv* env, jobject nativeImplObj, jint deviceId) { - NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - - return im->getInputManager()->getReader().isInputDeviceEnabled(deviceId); -} - static void nativeEnableInputDevice(JNIEnv* env, jobject nativeImplObj, jint deviceId) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -2593,27 +2463,12 @@ static void nativeDisableInputDevice(JNIEnv* env, jobject nativeImplObj, jint de im->setInputDeviceEnabled(deviceId, false); } -static void nativeSetPointerIconType(JNIEnv* env, jobject nativeImplObj, jint iconId) { - // iconId is set in java from from frameworks/base/core/java/android/view/PointerIcon.java, - // where the definition in <input/Input.h> is duplicated as a sealed class (type safe enum - // equivalent in Java). - - NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - - im->setPointerIconType(static_cast<PointerIconStyle>(iconId)); -} - static void nativeReloadPointerIcons(JNIEnv* env, jobject nativeImplObj) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); im->reloadPointerIcons(); } -static void nativeSetCustomPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj) { - NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->setCustomPointerIcon(toSpriteIcon(android_view_PointerIcon_toNative(env, iconObj))); -} - static bool nativeSetPointerIcon(JNIEnv* env, jobject nativeImplObj, jobject iconObj, jint displayId, jint deviceId, jint pointerId, jobject inputTokenObj) { @@ -2631,7 +2486,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)); } @@ -2639,13 +2494,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) { @@ -2657,8 +2513,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) { @@ -2794,7 +2651,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) { @@ -2812,7 +2669,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()); @@ -2857,6 +2714,11 @@ static void nativeSetInputMethodConnectionIsActive(JNIEnv* env, jobject nativeIm im->setInputMethodConnectionIsActive(isActive); } +static jint nativeGetLastUsedInputDeviceId(JNIEnv* env, jobject nativeImplObj) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + return static_cast<jint>(im->getInputManager()->getReader().getLastUsedInputDeviceId()); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -2930,13 +2792,9 @@ static const JNINativeMethod gInputManagerMethods[] = { {"sysfsNodeChanged", "(Ljava/lang/String;)V", (void*)nativeSysfsNodeChanged}, {"dump", "()Ljava/lang/String;", (void*)nativeDump}, {"monitor", "()V", (void*)nativeMonitor}, - {"isInputDeviceEnabled", "(I)Z", (void*)nativeIsInputDeviceEnabled}, {"enableInputDevice", "(I)V", (void*)nativeEnableInputDevice}, {"disableInputDevice", "(I)V", (void*)nativeDisableInputDevice}, - {"setPointerIconType", "(I)V", (void*)nativeSetPointerIconType}, {"reloadPointerIcons", "()V", (void*)nativeReloadPointerIcons}, - {"setCustomPointerIcon", "(Landroid/view/PointerIcon;)V", - (void*)nativeSetCustomPointerIcon}, {"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z", (void*)nativeSetPointerIcon}, {"setPointerIconVisibility", "(IZ)V", (void*)nativeSetPointerIconVisibility}, @@ -2968,6 +2826,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"setAccessibilityStickyKeysEnabled", "(Z)V", (void*)nativeSetAccessibilityStickyKeysEnabled}, {"setInputMethodConnectionIsActive", "(Z)V", (void*)nativeSetInputMethodConnectionIsActive}, + {"getLastUsedInputDeviceId", "()I", (void*)nativeGetLastUsedInputDeviceId}, }; #define FIND_CLASS(var, className) \ @@ -3060,9 +2919,6 @@ int register_android_server_InputManager(JNIEnv* env) { "dispatchUnhandledKey", "(Landroid/os/IBinder;Landroid/view/KeyEvent;I)Landroid/view/KeyEvent;"); - GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged", - "(IFF)V"); - GET_METHOD_ID(gServiceClassInfo.notifyStickyModifierStateChanged, clazz, "notifyStickyModifierStateChanged", "(II)V"); @@ -3078,8 +2934,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;"); @@ -3165,6 +3021,8 @@ int register_android_server_InputManager(JNIEnv* env) { env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_PLAYER_ID", "I"); gLightClassInfo.lightTypeKeyboardBacklight = env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_BACKLIGHT", "I"); + gLightClassInfo.lightTypeKeyboardMicMute = + env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_MIC_MUTE", "I"); gLightClassInfo.lightCapabilityBrightness = env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_BRIGHTNESS", "I"); gLightClassInfo.lightCapabilityColorRgb = 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/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd index 6143f1dd5b1c..610b502f2a07 100644 --- a/services/core/xsd/display-device-config/display-device-config.xsd +++ b/services/core/xsd/display-device-config/display-device-config.xsd @@ -746,6 +746,20 @@ minOccurs="0" maxOccurs="1"> <xs:annotation name="final"/> </xs:element> + <!-- list of supported modes for low power. Each point corresponds to one mode. + Mode format is : first = refreshRate, second = vsyncRate. E.g. : + <lowPowerSupportedModes> + <point> + <first>60</first> // refreshRate + <second>60</second> //vsyncRate + </point> + .... + </lowPowerSupportedModes> + --> + <xs:element type="nonNegativeFloatToFloatMap" name="lowPowerSupportedModes" minOccurs="0"> + <xs:annotation name="nullable"/> + <xs:annotation name="final"/> + </xs:element> </xs:complexType> <xs:complexType name="refreshRateZoneProfiles"> diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt index 45ec8f250efa..203a6d99dba1 100644 --- a/services/core/xsd/display-device-config/schema/current.txt +++ b/services/core/xsd/display-device-config/schema/current.txt @@ -360,6 +360,7 @@ package com.android.server.display.config { method public final java.math.BigInteger getDefaultRefreshRateInHbmHdr(); method public final java.math.BigInteger getDefaultRefreshRateInHbmSunlight(); method public final com.android.server.display.config.BlockingZoneConfig getHigherBlockingZoneConfigs(); + method @Nullable public final com.android.server.display.config.NonNegativeFloatToFloatMap getLowPowerSupportedModes(); method public final com.android.server.display.config.BlockingZoneConfig getLowerBlockingZoneConfigs(); method public final com.android.server.display.config.RefreshRateZoneProfiles getRefreshRateZoneProfiles(); method public final void setDefaultPeakRefreshRate(java.math.BigInteger); @@ -367,6 +368,7 @@ package com.android.server.display.config { method public final void setDefaultRefreshRateInHbmHdr(java.math.BigInteger); method public final void setDefaultRefreshRateInHbmSunlight(java.math.BigInteger); method public final void setHigherBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig); + method public final void setLowPowerSupportedModes(@Nullable com.android.server.display.config.NonNegativeFloatToFloatMap); method public final void setLowerBlockingZoneConfigs(com.android.server.display.config.BlockingZoneConfig); method public final void setRefreshRateZoneProfiles(com.android.server.display.config.RefreshRateZoneProfiles); } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index 6ef14366b9e7..2e126f1b50ba 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -66,6 +66,7 @@ import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.server.credentials.metrics.ApiName; import com.android.server.credentials.metrics.ApiStatus; @@ -294,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, @@ -1166,11 +1167,17 @@ public final class CredentialManagerService settingsWrapper.getStringForUser( Settings.Secure.AUTOFILL_SERVICE, UserHandle.myUserId()); - // If there is an autofill provider and it is the placeholder indicating + // If there is an autofill provider and it is the credential autofill service indicating // that the currently selected primary provider does not support autofill - // then we should wipe the setting to keep it in sync. - if (autofillProvider != null && primaryProviders.isEmpty()) { - if (autofillProvider.equals(AUTOFILL_PLACEHOLDER_VALUE)) { + // then we should keep as is + String credentialAutofillService = settingsWrapper.mContext.getResources().getString( + R.string.config_defaultCredentialManagerAutofillService); + if (autofillProvider != null && primaryProviders.isEmpty() && !TextUtils.equals( + autofillProvider, credentialAutofillService)) { + // If the existing autofill provider is from the app being removed + // then erase the autofill service setting. + ComponentName cn = ComponentName.unflattenFromString(autofillProvider); + if (cn != null && cn.getPackageName().equals(packageName)) { if (!settingsWrapper.putStringForUser( Settings.Secure.AUTOFILL_SERVICE, "", @@ -1178,19 +1185,6 @@ public final class CredentialManagerService /* overrideableByRestore= */ true)) { Slog.e(TAG, "Failed to remove autofill package: " + packageName); } - } else { - // If the existing autofill provider is from the app being removed - // then erase the autofill service setting. - ComponentName cn = ComponentName.unflattenFromString(autofillProvider); - if (cn != null && cn.getPackageName().equals(packageName)) { - if (!settingsWrapper.putStringForUser( - Settings.Secure.AUTOFILL_SERVICE, - "", - UserHandle.myUserId(), - /* overrideableByRestore= */ true)) { - Slog.e(TAG, "Failed to remove autofill package: " + packageName); - } - } } } 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/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java index 950ec77f5ba8..502607be7310 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java @@ -19,7 +19,6 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.BooleanPolicyValue; -import android.app.admin.PolicyKey; import android.util.Log; import com.android.modules.utils.TypedXmlPullParser; @@ -37,8 +36,7 @@ final class BooleanPolicySerializer extends PolicySerializer<Boolean> { private static final String TAG = "BooleanPolicySerializer"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Boolean value) - throws IOException { + void saveToXml(TypedXmlSerializer serializer, @NonNull Boolean value) throws IOException { Objects.requireNonNull(value); serializer.attributeBoolean(/* namespace= */ null, ATTR_VALUE, value); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java index d24afabe95a4..a65c7e1870f3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java @@ -18,8 +18,6 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.app.admin.BundlePolicyValue; -import android.app.admin.PackagePolicyKey; -import android.app.admin.PolicyKey; import android.os.Bundle; import android.os.Parcelable; import android.util.Log; @@ -53,14 +51,8 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { private static final String ATTR_TYPE_BUNDLE_ARRAY = "BA"; @Override - void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer, - @NonNull Bundle value) throws IOException { + void saveToXml(TypedXmlSerializer serializer, @NonNull Bundle value) throws IOException { Objects.requireNonNull(value); - Objects.requireNonNull(policyKey); - if (!(policyKey instanceof PackagePolicyKey)) { - throw new IllegalArgumentException("policyKey is not of type " - + "PackagePolicyKey"); - } writeBundle(value, serializer); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java index 6303a1a8b860..01f56e07e157 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java @@ -19,7 +19,6 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.ComponentNamePolicyValue; -import android.app.admin.PolicyKey; import android.content.ComponentName; import android.util.Log; @@ -37,8 +36,7 @@ final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName private static final String ATTR_CLASS_NAME = "class-name"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, - @NonNull ComponentName value) throws IOException { + void saveToXml(TypedXmlSerializer serializer, @NonNull ComponentName value) throws IOException { Objects.requireNonNull(value); serializer.attribute( /* namespace= */ null, ATTR_PACKAGE_NAME, value.getPackageName()); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index d114337f9c6c..d733762e90e5 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -217,7 +217,7 @@ final class DevicePolicyEngine { <V> void setLocalPolicy( @NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin, - @Nullable PolicyValue<V> value, + @NonNull PolicyValue<V> value, int userId, boolean skipEnforcePolicy) { Objects.requireNonNull(policyDefinition); @@ -313,6 +313,7 @@ final class DevicePolicyEngine { } updateDeviceAdminServiceOnPolicyAddLocked(enforcingAdmin); write(); + applyToInheritableProfiles(policyDefinition, enforcingAdmin, value, userId); } // TODO: add more documentation on broadcasts/callbacks to use to get current enforced values @@ -400,7 +401,7 @@ final class DevicePolicyEngine { * else remove the policy from child. */ private <V> void applyToInheritableProfiles(PolicyDefinition<V> policyDefinition, - EnforcingAdmin enforcingAdmin, PolicyValue<V> value, int userId) { + EnforcingAdmin enforcingAdmin, @Nullable PolicyValue<V> value, int userId) { if (policyDefinition.isInheritable()) { Binder.withCleanCallingIdentity(() -> { List<UserInfo> userInfos = mUserManager.getProfiles(userId); @@ -1742,14 +1743,17 @@ final class DevicePolicyEngine { } } - <V> void reapplyAllPoliciesLocked() { + <V> void reapplyAllPoliciesOnBootLocked() { for (PolicyKey policy : mGlobalPolicies.keySet()) { PolicyState<?> policyState = mGlobalPolicies.get(policy); // Policy definition and value will always be of the same type PolicyDefinition<V> policyDefinition = (PolicyDefinition<V>) policyState.getPolicyDefinition(); - PolicyValue<V> policyValue = (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); - enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL); + if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) { + PolicyValue<V> policyValue = + (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); + enforcePolicy(policyDefinition, policyValue, UserHandle.USER_ALL); + } } for (int i = 0; i < mLocalPolicies.size(); i++) { int userId = mLocalPolicies.keyAt(i); @@ -1758,10 +1762,11 @@ final class DevicePolicyEngine { // Policy definition and value will always be of the same type PolicyDefinition<V> policyDefinition = (PolicyDefinition<V>) policyState.getPolicyDefinition(); - PolicyValue<V> policyValue = - (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); - enforcePolicy(policyDefinition, policyValue, userId); - + if (!policyDefinition.shouldSkipEnforcementIfNotChanged()) { + PolicyValue<V> policyValue = + (PolicyValue<V>) policyState.getCurrentResolvedPolicy(); + enforcePolicy(policyDefinition, policyValue, userId); + } } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 85ab562902e2..85d2a0df4fdc 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; @@ -350,7 +351,6 @@ import android.app.admin.PreferentialNetworkServiceConfig; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; import android.app.admin.StartInstallingUpdateCallback; -import android.app.admin.StringSetPolicyValue; import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; import android.app.admin.UnsafeStateException; @@ -455,10 +455,10 @@ import android.security.IKeyChainAliasCallback; import android.security.IKeyChainService; import android.security.KeyChain; import android.security.KeyChain.KeyChainConnection; -import android.security.KeyStore; import android.security.keymaster.KeymasterCertificateChain; import android.security.keystore.AttestationUtils; import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.security.keystore.ParcelableKeyGenParameterSpec; import android.stats.devicepolicy.DevicePolicyEnums; import android.telecom.TelecomManager; @@ -1985,11 +1985,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { CryptoTestHelper.runAndLogSelfTest(); } - public String[] getPersonalAppsForSuspension(@UserIdInt int userId) { - return PersonalAppsSuspensionHelper.forUser(mContext, userId) - .getPersonalAppsForSuspension(); - } - public long systemCurrentTimeMillis() { return System.currentTimeMillis(); } @@ -2723,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()); @@ -3355,7 +3351,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { break; case SystemService.PHASE_SYSTEM_SERVICES_READY: synchronized (getLockObject()) { - mDevicePolicyEngine.reapplyAllPoliciesLocked(); + mDevicePolicyEngine.reapplyAllPoliciesOnBootLocked(); } break; case SystemService.PHASE_ACTIVITY_MANAGER_READY: @@ -6248,7 +6244,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try (KeyChainConnection keyChainConnection = KeyChain.bindAsUser(mContext, caller.getUserHandle())) { IKeyChainService keyChain = keyChainConnection.getService(); - if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyStore.UID_SELF)) { + if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyProperties.UID_SELF)) { logInstallKeyPairFailure(caller, isCredentialManagementApp); return false; } @@ -6583,7 +6579,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // As the caller will be granted access to the key, ensure no UID was specified, as // it will not have the desired effect. - if (keySpec.getUid() != KeyStore.UID_SELF) { + if (keySpec.getUid() != KeyProperties.UID_SELF) { Slogf.e(LOG_TAG, "Only the caller can be granted access to the generated keypair."); logGenerateKeyPairFailure(caller, isCredentialManagementApp); return false; @@ -11447,7 +11443,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } setBackwardsCompatibleAppRestrictions( caller, packageName, restrictions, caller.getUserHandle()); - } else if (Flags.dmrhCanSetAppRestriction()) { + } else if (Flags.dmrhSetAppRestrictions()) { final boolean isRoleHolder; if (who != null) { // DO or PO @@ -11488,10 +11484,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { new BundlePolicyValue(restrictions), affectedUserId); } - Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); - changeIntent.setPackage(packageName); - changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); - mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId)); } else { mInjector.binderWithCleanCallingIdentity(() -> { mUserManager.setApplicationRestrictions(packageName, restrictions, @@ -12078,7 +12070,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.PERMITTED_INPUT_METHODS, admin, - new StringSetPolicyValue(new HashSet<>(packageList)), + new PackageSetPolicyValue(new HashSet<>(packageList)), userId); } } @@ -12849,7 +12841,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Bundle.EMPTY; } return policies.get(enforcingAdmin).getValue(); - } else if (Flags.dmrhCanSetAppRestriction()) { + } else if (Flags.dmrhSetAppRestrictions()) { final boolean isRoleHolder; if (who != null) { // Caller is DO or PO. They cannot call this on parent @@ -15177,10 +15169,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) { @@ -15776,8 +15766,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), userId); List<Bundle> restrictions = new ArrayList<>(); - for (EnforcingAdmin admin : policies.keySet()) { - restrictions.add(policies.get(admin).getValue()); + for (PolicyValue<Bundle> policyValue: policies.values()) { + Bundle value = policyValue.getValue(); + // Probably not necessary since setApplicationRestrictions only sets non-empty + // Bundle, but just in case. + if (value != null && !value.isEmpty()) { + restrictions.add(value); + } } return mInjector.binderWithCleanCallingIdentity(() -> { @@ -20363,12 +20358,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.setGlobalPolicy( PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES, enforcingAdmin, - new StringSetPolicyValue(packages)); + new PackageSetPolicyValue(packages)); } else { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES, enforcingAdmin, - new StringSetPolicyValue(packages), + new PackageSetPolicyValue(packages), caller.getUserId()); } } @@ -21610,9 +21605,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { == HEADLESS_DEVICE_OWNER_MODE_SINGLE_USER; } - if (Flags.headlessSingleUserFixes() && mInjector.userManagerIsHeadlessSystemUserMode() - && isSingleUserMode && !mInjector.isChangeEnabled( - PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), caller.getUserId())) { + if (Flags.headlessSingleMinTargetSdk() + && mInjector.userManagerIsHeadlessSystemUserMode() + && isSingleUserMode + && !mInjector.isChangeEnabled( + PROVISION_SINGLE_USER_MODE, deviceAdmin.getPackageName(), + caller.getUserId())) { throw new IllegalStateException("Device admin is not targeting Android V."); } @@ -24047,7 +24045,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.PERMITTED_INPUT_METHODS, enforcingAdmin, - new StringSetPolicyValue( + new PackageSetPolicyValue( new HashSet<>(admin.permittedInputMethods)), admin.getUserHandle().getIdentifier()); } @@ -24056,7 +24054,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.PERMITTED_INPUT_METHODS, enforcingAdmin, - new StringSetPolicyValue( + new PackageSetPolicyValue( new HashSet<>(admin.getParentActiveAdmin() .permittedInputMethods)), getProfileParentId(admin.getUserHandle().getIdentifier())); @@ -24112,12 +24110,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mDevicePolicyEngine.setGlobalPolicy( PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES, enforcingAdmin, - new StringSetPolicyValue(new HashSet<>(admin.protectedPackages))); + new PackageSetPolicyValue( + new HashSet<>(admin.protectedPackages))); } else { mDevicePolicyEngine.setLocalPolicy( PolicyDefinition.USER_CONTROLLED_DISABLED_PACKAGES, enforcingAdmin, - new StringSetPolicyValue(new HashSet<>(admin.protectedPackages)), + new PackageSetPolicyValue( + new HashSet<>(admin.protectedPackages)), admin.getUserHandle().getIdentifier()); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java index 1000bfa5f6c9..cbd28475bc26 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/EnterpriseSpecificIdCalculator.java @@ -52,7 +52,18 @@ class EnterpriseSpecificIdCalculator { EnterpriseSpecificIdCalculator(Context context) { TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class); Preconditions.checkState(telephonyService != null, "Unable to access telephony service"); - mImei = telephonyService.getImei(0); + + String imei; + try { + imei = telephonyService.getImei(0); + } catch (UnsupportedOperationException doesNotSupportGms) { + // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM. + // However that runs the risk of changing a device's existing ESID if on these devices + // telephonyService.getImei() actually returns non-null even when the device does not + // declare FEATURE_TELEPHONY_GSM. + imei = null; + } + mImei = imei; String meid; try { meid = telephonyService.getMeid(0); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java index 45a2d2a7bda1..ebbf22cfef69 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java @@ -19,7 +19,6 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.IntegerPolicyValue; -import android.app.admin.PolicyKey; import android.util.Log; import com.android.modules.utils.TypedXmlPullParser; @@ -37,8 +36,7 @@ final class IntegerPolicySerializer extends PolicySerializer<Integer> { private static final String ATTR_VALUE = "value"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, - @NonNull Integer value) throws IOException { + void saveToXml(TypedXmlSerializer serializer, @NonNull Integer value) throws IOException { Objects.requireNonNull(value); serializer.attributeInt(/* namespace= */ null, ATTR_VALUE, value); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java index 20bd2d75f846..13412d05aba3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java @@ -18,7 +18,6 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.app.admin.LockTaskPolicy; -import android.app.admin.PolicyKey; import android.util.Log; import com.android.modules.utils.TypedXmlPullParser; @@ -39,8 +38,8 @@ final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> { private static final String ATTR_FLAGS = "flags"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, - @NonNull LockTaskPolicy value) throws IOException { + void saveToXml(TypedXmlSerializer serializer, @NonNull LockTaskPolicy value) + throws IOException { Objects.requireNonNull(value); serializer.attribute( /* namespace= */ null, diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java index 522c4b5e84be..c363e6626de3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java @@ -19,7 +19,6 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.admin.LongPolicyValue; -import android.app.admin.PolicyKey; import android.util.Log; import com.android.modules.utils.TypedXmlPullParser; @@ -37,8 +36,7 @@ final class LongPolicySerializer extends PolicySerializer<Long> { private static final String ATTR_VALUE = "value"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, - @NonNull Long value) throws IOException { + void saveToXml(TypedXmlSerializer serializer, @NonNull Long value) throws IOException { Objects.requireNonNull(value); serializer.attributeLong(/* namespace= */ null, ATTR_VALUE, value); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java index 0265453eecc7..c4da029c6315 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetPolicySerializer.java @@ -18,9 +18,8 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.admin.PolicyKey; import android.app.admin.PolicyValue; -import android.app.admin.StringSetPolicyValue; +import android.app.admin.PackageSetPolicyValue; import android.util.Log; import com.android.modules.utils.TypedXmlPullParser; @@ -31,12 +30,11 @@ import java.util.Objects; import java.util.Set; // TODO(scottjonathan): Replace with generic set implementation -final class StringSetPolicySerializer extends PolicySerializer<Set<String>> { +final class PackageSetPolicySerializer extends PolicySerializer<Set<String>> { private static final String ATTR_VALUES = "strings"; private static final String ATTR_VALUES_SEPARATOR = ";"; @Override - void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, - @NonNull Set<String> value) throws IOException { + void saveToXml(TypedXmlSerializer serializer, @NonNull Set<String> value) throws IOException { Objects.requireNonNull(value); serializer.attribute( /* namespace= */ null, ATTR_VALUES, String.join(ATTR_VALUES_SEPARATOR, value)); @@ -47,10 +45,10 @@ final class StringSetPolicySerializer extends PolicySerializer<Set<String>> { PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser) { String valuesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_VALUES); if (valuesStr == null) { - Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value."); + Log.e(DevicePolicyEngine.TAG, "Error parsing PackageSet policy value."); return null; } Set<String> values = Set.of(valuesStr.split(ATTR_VALUES_SEPARATOR)); - return new StringSetPolicyValue(values); + return new PackageSetPolicyValue(values); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java index 5298960892a3..d1e241b365a6 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetUnion.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PackageSetUnion.java @@ -18,14 +18,15 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; import android.app.admin.PolicyValue; -import android.app.admin.StringSetPolicyValue; +import android.app.admin.PackageSetPolicyValue; +import android.app.admin.StringSetUnion; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Objects; import java.util.Set; -final class StringSetUnion extends ResolutionMechanism<Set<String>> { +final class PackageSetUnion extends ResolutionMechanism<Set<String>> { @Override PolicyValue<Set<String>> resolve( @@ -38,17 +39,17 @@ final class StringSetUnion extends ResolutionMechanism<Set<String>> { for (PolicyValue<Set<String>> policy : adminPolicies.values()) { unionOfPolicies.addAll(policy.getValue()); } - return new StringSetPolicyValue(unionOfPolicies); + return new PackageSetPolicyValue(unionOfPolicies); } @Override - android.app.admin.StringSetUnion getParcelableResolutionMechanism() { - return new android.app.admin.StringSetUnion(); + StringSetUnion getParcelableResolutionMechanism() { + return new StringSetUnion(); } @Override public String toString() { - return "SetUnion {}"; + return "PackageSetUnion {}"; } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java index 8cb511e8727c..7483b43baf13 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java @@ -37,7 +37,6 @@ import android.provider.Telephony; import android.text.TextUtils; import android.util.ArraySet; import android.util.IndentingPrintWriter; -import android.util.Log; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.IAccessibilityManager; import android.view.inputmethod.InputMethodInfo; @@ -107,10 +106,6 @@ public final class PersonalAppsSuspensionHelper { for (final String pkg : unsuspendablePackages) { result.remove(pkg); } - - if (Log.isLoggable(LOG_TAG, Log.INFO)) { - Slogf.i(LOG_TAG, "Packages subject to suspension: %s", String.join(",", result)); - } return result.toArray(new String[0]); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 7a9fa0fb5658..8bec3847d8ca 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -51,6 +51,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; final class PolicyDefinition<V> { @@ -82,6 +83,10 @@ final class PolicyDefinition<V> { // them. private static final int POLICY_FLAG_USER_RESTRICTION_POLICY = 1 << 4; + // Only invoke the policy enforcer callback when the policy value changes, and do not invoke the + // callback in other cases such as device reboots. + private static final int POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED = 1 << 5; + private static final MostRestrictive<Boolean> FALSE_MORE_RESTRICTIVE = new MostRestrictive<>( List.of(new BooleanPolicyValue(false), new BooleanPolicyValue(true))); @@ -162,9 +167,9 @@ final class PolicyDefinition<V> { new PolicyDefinition<>( new NoArgsPolicyKey( DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY), - new StringSetUnion(), + new PackageSetUnion(), PolicyEnforcerCallbacks::setUserControlDisabledPackages, - new StringSetPolicySerializer()); + new PackageSetPolicySerializer()); // This is saved in the static map sPolicyDefinitions so that we're able to reconstruct the // actual policy with the correct arguments (i.e. packageName) when reading the policies from @@ -231,11 +236,11 @@ final class PolicyDefinition<V> { // Don't need to take in a resolution mechanism since its never used, but might // need some refactoring to not always assume a non-null mechanism. new MostRecent<>(), - POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_NON_COEXISTABLE_POLICY, - // Application restrictions are now stored and retrieved from DPMS, so no - // enforcing is required, however DPMS calls into UM to set restrictions for - // backwards compatibility. - (Bundle value, Context context, Integer userId, PolicyKey policyKey) -> true, + // Only invoke the enforcement callback during policy change and not other state + POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE + | POLICY_FLAG_NON_COEXISTABLE_POLICY + | POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED, + PolicyEnforcerCallbacks::setApplicationRestrictions, new BundlePolicySerializer()); /** @@ -328,7 +333,7 @@ final class PolicyDefinition<V> { new MostRecent<>(), POLICY_FLAG_LOCAL_ONLY_POLICY | POLICY_FLAG_INHERITABLE, (Set<String> value, Context context, Integer userId, PolicyKey policyKey) -> true, - new StringSetPolicySerializer()); + new PackageSetPolicySerializer()); static PolicyDefinition<Boolean> SCREEN_CAPTURE_DISABLED = new PolicyDefinition<>( @@ -581,6 +586,10 @@ final class PolicyDefinition<V> { return (mPolicyFlags & POLICY_FLAG_USER_RESTRICTION_POLICY) != 0; } + boolean shouldSkipEnforcementIfNotChanged() { + return (mPolicyFlags & POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED) != 0; + } + @Nullable PolicyValue<V> resolvePolicy(LinkedHashMap<EnforcingAdmin, PolicyValue<V>> adminsPolicy) { return mResolutionMechanism.resolve(adminsPolicy); @@ -610,7 +619,7 @@ final class PolicyDefinition<V> { * {@link Object#equals} implementation. */ private PolicyDefinition( - PolicyKey key, + @NonNull PolicyKey key, ResolutionMechanism<V> resolutionMechanism, QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback, PolicySerializer<V> policySerializer) { @@ -622,11 +631,12 @@ final class PolicyDefinition<V> { * {@link Object#equals} and {@link Object#hashCode()} implementation. */ private PolicyDefinition( - PolicyKey policyKey, + @NonNull PolicyKey policyKey, ResolutionMechanism<V> resolutionMechanism, int policyFlags, QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback, PolicySerializer<V> policySerializer) { + Objects.requireNonNull(policyKey); mPolicyKey = policyKey; mResolutionMechanism = resolutionMechanism; mPolicyFlags = policyFlags; @@ -684,7 +694,7 @@ final class PolicyDefinition<V> { void savePolicyValueToXml(TypedXmlSerializer serializer, V value) throws IOException { - mPolicySerializer.saveToXml(mPolicyKey, serializer, value); + mPolicySerializer.saveToXml(serializer, value); } @Nullable diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index eeb49765cc9d..04d277e6e667 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -37,11 +37,13 @@ import android.app.admin.flags.Flags; import android.app.usage.UsageStatsManagerInternal; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.Binder; +import android.os.Bundle; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -172,6 +174,29 @@ final class PolicyEnforcerCallbacks { return true; } + + /** + * Application restrictions are stored and retrieved from DPMS, so no enforcing (aka pushing + * it to UMS) is required. Only need to send broadcast to the target user here as we rely on + * the inheritable policy propagation logic in PolicyEngine to apply this policy to multiple + * profiles. The broadcast should only be sent when an application restriction is set, so we + * rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback + * when the policy is set, and not during system boot or other situations. + */ + static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId, + PolicyKey policyKey) { + Binder.withCleanCallingIdentity(() -> { + PackagePolicyKey key = (PackagePolicyKey) policyKey; + String packageName = key.getPackageName(); + Objects.requireNonNull(packageName); + Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); + changeIntent.setPackage(packageName); + changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId)); + }); + return true; + } + private static class BlockingCallback { private final CountDownLatch mLatch = new CountDownLatch(1); private final AtomicReference<Boolean> mValue = new AtomicReference<>(); @@ -196,19 +221,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; @@ -375,6 +408,7 @@ final class PolicyEnforcerCallbacks { private static void suspendPersonalAppsInPackageManager(Context context, int userId) { final String[] appsToSuspend = PersonalAppsSuspensionHelper.forUser(context, userId) .getPersonalAppsForSuspension(); + Slogf.i(LOG_TAG, "Suspending personal apps: %s", String.join(",", appsToSuspend)); final String[] failedApps = LocalServices.getService(PackageManagerInternal.class) .setPackagesSuspendedByAdmin(userId, appsToSuspend, true); if (!ArrayUtils.isEmpty(failedApps)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java index 5af2fa285483..e83b031fd3c0 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java @@ -17,7 +17,6 @@ package com.android.server.devicepolicy; import android.annotation.NonNull; -import android.app.admin.PolicyKey; import android.app.admin.PolicyValue; import com.android.modules.utils.TypedXmlPullParser; @@ -26,7 +25,6 @@ import com.android.modules.utils.TypedXmlSerializer; import java.io.IOException; abstract class PolicySerializer<V> { - abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull V value) - throws IOException; + abstract void saveToXml(TypedXmlSerializer serializer, @NonNull V value) throws IOException; abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 8caf5aedda49..cfe4e17eb1be 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -47,6 +47,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.res.Configuration; import android.content.res.Resources.Theme; +import android.crashrecovery.flags.Flags; import android.credentials.CredentialManager; import android.database.sqlite.SQLiteCompatibilityWalFlags; import android.database.sqlite.SQLiteGlobal; @@ -1195,11 +1196,13 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(RecoverySystemService.Lifecycle.class); t.traceEnd(); - // Now that we have the bare essentials of the OS up and running, take - // note that we just booted, which might send out a rescue party if - // we're stuck in a runtime restart loop. - RescueParty.registerHealthObserver(mSystemContext); - PackageWatchdog.getInstance(mSystemContext).noteBoot(); + if (!Flags.recoverabilityDetection()) { + // Now that we have the bare essentials of the OS up and running, take + // note that we just booted, which might send out a rescue party if + // we're stuck in a runtime restart loop. + RescueParty.registerHealthObserver(mSystemContext); + PackageWatchdog.getInstance(mSystemContext).noteBoot(); + } // Manages LEDs and display backlight so we need it to bring up the display. t.traceBegin("StartLightsService"); @@ -1469,9 +1472,12 @@ public final class SystemServer implements Dumpable { boolean enableVrService = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE); - // For debugging RescueParty - if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_system", false)) { - throw new RuntimeException(); + if (!Flags.recoverabilityDetection()) { + // For debugging RescueParty + if (Build.IS_DEBUGGABLE + && SystemProperties.getBoolean("debug.crash_system", false)) { + throw new RuntimeException(); + } } try { @@ -1618,7 +1624,8 @@ public final class SystemServer implements Dumpable { wm = WindowManagerService.main(context, inputManager, !mFirstBoot, new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager); ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false, - DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO); + DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_HIGH + | DUMP_FLAG_PROTO); ServiceManager.addService(Context.INPUT_SERVICE, inputManager, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); t.traceEnd(); @@ -2909,6 +2916,14 @@ public final class SystemServer implements Dumpable { mPackageManagerService.systemReady(); t.traceEnd(); + if (Flags.recoverabilityDetection()) { + // Now that we have the essential services needed for rescue party, initialize + // RescuParty. note that we just booted, which might send out a rescue party if + // we're stuck in a runtime restart loop. + RescueParty.registerHealthObserver(mSystemContext); + PackageWatchdog.getInstance(mSystemContext).noteBoot(); + } + t.traceBegin("MakeDisplayManagerServiceReady"); try { // TODO: use boot phase and communicate this flag some other way @@ -3312,6 +3327,14 @@ public final class SystemServer implements Dumpable { * are updated outside of OTA; and to avoid breaking dependencies from system into apexes. */ private void startApexServices(@NonNull TimingsTraceAndSlog t) { + if (Flags.recoverabilityDetection()) { + // For debugging RescueParty + if (Build.IS_DEBUGGABLE + && SystemProperties.getBoolean("debug.crash_system", false)) { + throw new RuntimeException(); + } + } + t.traceBegin("startApexServices"); // TODO(b/192880996): get the list from "android" package, once the manifest entries // are migrated to system manifest. diff --git a/services/people/java/com/android/server/people/TEST_MAPPING b/services/people/java/com/android/server/people/TEST_MAPPING new file mode 100644 index 000000000000..55b355cbc991 --- /dev/null +++ b/services/people/java/com/android/server/people/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "presubmit": [ + { + "name": "FrameworksServicesTests", + "options": [ + { + "include-filter": "com.android.server.people.data" + } + ] + } + ] +}
\ No newline at end of file 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 87af841b901c..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 @@ -63,11 +63,8 @@ class AppIdPermissionPolicy : SchemePolicy() { private val privilegedPermissionAllowlistViolations = MutableIndexedSet<String>() - /** - * Test-only switch to enforce signature permission allowlist even on debuggable builds. - */ - @Volatile - var isSignaturePermissionAllowlistForceEnforced = false + /** Test-only switch to enforce signature permission allowlist even on debuggable builds. */ + @Volatile var isSignaturePermissionAllowlistForceEnforced = false override val subjectScheme: String get() = UidUri.SCHEME @@ -108,8 +105,8 @@ class AppIdPermissionPolicy : SchemePolicy() { val changedPermissionNames = MutableIndexedSet<String>() packageNames.forEachIndexed { _, packageName -> // The package may still be removed even if it was once notified as installed. - val packageState = newState.externalState.packageStates[packageName] - ?: return@forEachIndexed + val packageState = + newState.externalState.packageStates[packageName] ?: return@forEachIndexed adoptPermissions(packageState, changedPermissionNames) addPermissionGroups(packageState) addPermissions(packageState, changedPermissionNames) @@ -122,14 +119,14 @@ class AppIdPermissionPolicy : SchemePolicy() { } packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName] - ?: return@forEachIndexed + val packageState = + newState.externalState.packageStates[packageName] ?: return@forEachIndexed val installedPackageState = if (isSystemUpdated) packageState else null evaluateAllPermissionStatesForPackage(packageState, installedPackageState) } packageNames.forEachIndexed { _, packageName -> - val packageState = newState.externalState.packageStates[packageName] - ?: return@forEachIndexed + val packageState = + newState.externalState.packageStates[packageName] ?: return@forEachIndexed newState.externalState.userIds.forEachIndexed { _, userId -> inheritImplicitPermissionStates(packageState.appId, userId) } @@ -1279,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 + ) } } @@ -1775,6 +1788,7 @@ class AppIdPermissionPolicy : SchemePolicy() { Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.READ_MEDIA_VIDEO, + Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, ) private val NEARBY_DEVICES_PERMISSIONS = 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 65feeb027b3e..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 @@ -2027,7 +2027,7 @@ class PermissionService(private val service: AccessCheckingService) : val writer = IndentingPrintWriter(pw, " ") - if (args.isNullOrEmpty()) { + if (args.isNullOrEmpty() || args[0] == "-a") { service.getState { writer.dumpSystemState(state) getAllAppIdPackageNames(state).forEachIndexed { _, appId, packageNames -> @@ -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 68038fa87ae0..488fe57cf6f8 100644 --- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java +++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java @@ -63,6 +63,9 @@ public final class ProfcollectForwardingService extends SystemService { "com.android.server.profcollect.UPLOAD_PROFILES"; private static final long BG_PROCESS_INTERVAL = TimeUnit.HOURS.toMillis(4); // every 4 hours. + private int mUsageSetting; + private boolean mUploadEnabled; + private IProfCollectd mIProfcollect; private static ProfcollectForwardingService sSelfService; private final Handler mHandler = new ProfcollectdHandler(IoThread.getHandler().getLooper()); @@ -78,7 +81,7 @@ public final class ProfcollectForwardingService extends SystemService { public void onReceive(Context context, Intent intent) { if (INTENT_UPLOAD_PROFILES.equals(intent.getAction())) { Log.d(LOG_TAG, "Received broadcast to pack and upload reports"); - packAndUploadReport(); + createAndUploadReport(sSelfService); } } }; @@ -91,6 +94,17 @@ public final class ProfcollectForwardingService extends SystemService { } sSelfService = this; + // Get "Usage & diagnostics" checkbox status. 1 is for enabled, 0 is for disabled. + try { + mUsageSetting = Settings.Global.getInt(context.getContentResolver(), "multi_cb"); + } catch (SettingNotFoundException e) { + Log.e(LOG_TAG, "Usage setting not found: " + e.getMessage()); + mUsageSetting = -1; + } + + mUploadEnabled = + context.getResources().getBoolean(R.bool.config_profcollectReportUploaderEnabled); + final IntentFilter filter = new IntentFilter(); filter.addAction(INTENT_UPLOAD_PROFILES); context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED); @@ -221,7 +235,6 @@ public final class ProfcollectForwardingService extends SystemService { */ public static void schedule(Context context) { JobScheduler js = context.getSystemService(JobScheduler.class); - js.schedule(new JobInfo.Builder(JOB_IDLE_PROCESS, JOB_SERVICE_NAME) .setRequiresDeviceIdle(true) .setRequiresCharging(true) @@ -235,19 +248,8 @@ public final class ProfcollectForwardingService extends SystemService { if (DEBUG) { Log.d(LOG_TAG, "Starting background process job"); } - - BackgroundThread.get().getThreadHandler().post( - () -> { - try { - if (sSelfService.mIProfcollect == null) { - return; - } - sSelfService.mIProfcollect.process(); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Failed to process profiles in background: " - + e.getMessage()); - } - }); + createAndUploadReport(sSelfService); + jobFinished(params, false); return true; } @@ -356,7 +358,7 @@ public final class ProfcollectForwardingService extends SystemService { } if (status == UpdateEngine.UpdateStatusConstants.UPDATED_NEED_REBOOT) { - packAndUploadReport(); + createAndUploadReport(sSelfService); } } @@ -367,41 +369,27 @@ public final class ProfcollectForwardingService extends SystemService { }); } - private void packAndUploadReport() { - if (mIProfcollect == null) { + private static void createAndUploadReport(ProfcollectForwardingService pfs) { + String reportName; + try { + reportName = pfs.mIProfcollect.report(pfs.mUsageSetting) + ".zip"; + } catch (RemoteException e) { + Log.e(LOG_TAG, "Failed to create report: " + e.getMessage()); + return; + } + if (!pfs.mUploadEnabled) { + Log.i(LOG_TAG, "Upload is not enabled."); return; } - - Context context = getContext(); BackgroundThread.get().getThreadHandler().post(() -> { - try { - int usageSetting = -1; - try { - // Get "Usage & diagnostics" checkbox status. 1 is for enabled, 0 is for - // disabled. - usageSetting = Settings.Global.getInt(context.getContentResolver(), "multi_cb"); - } catch (SettingNotFoundException e) { - Log.i(LOG_TAG, "Usage setting not found: " + e.getMessage()); - } - - // Prepare profile report - String reportName = mIProfcollect.report(usageSetting) + ".zip"; - - if (!context.getResources().getBoolean( - R.bool.config_profcollectReportUploaderEnabled)) { - Log.i(LOG_TAG, "Upload is not enabled."); - return; - } - - // Upload the report - Intent intent = new Intent() - .setPackage("com.android.shell") - .setAction("com.android.shell.action.PROFCOLLECT_UPLOAD") - .putExtra("filename", reportName); - context.sendBroadcast(intent); - } catch (RemoteException e) { - Log.e(LOG_TAG, "Failed to upload report: " + e.getMessage()); - } + Intent intent = new Intent() + .setPackage("com.android.shell") + .setAction("com.android.shell.action.PROFCOLLECT_UPLOAD") + .putExtra("filename", reportName); + pfs.getContext().sendBroadcast(intent); }); + if (DEBUG) { + Log.d(LOG_TAG, "Sent report for upload."); + } } } 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/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index 1d225ba09bbd..221a99102daa 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -36,9 +36,10 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static java.util.Objects.requireNonNull; + import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -72,7 +73,10 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe super.setUp(); mVisibilityApplier = (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier(); - mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class)); + synchronized (ImfLock.class) { + mInputMethodManagerService.setAttachedClientForTesting(requireNonNull( + mInputMethodManagerService.getClientStateLocked(mMockInputMethodClient))); + } } @Test diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java index 1f0a37509989..70903cbc2b94 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java @@ -77,9 +77,13 @@ public class InputMethodBindingControllerTest extends InputMethodManagerServiceT mCountDownLatch = new CountDownLatch(1); // Remove flag Context.BIND_SCHEDULE_LIKE_TOP_APP because in tests we are not calling // from system. - mBindingController = - new InputMethodBindingController( - mInputMethodManagerService, mImeConnectionBindFlags, mCountDownLatch); + synchronized (ImfLock.class) { + mBindingController = + new InputMethodBindingController( + mInputMethodManagerService.getCurrentImeUserIdLocked(), + mInputMethodManagerService, mImeConnectionBindFlags, + mCountDownLatch); + } } @Test diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java new file mode 100644 index 000000000000..50804da5a293 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodInfoUtilsTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod; + +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1; +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2; +import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo; +import static com.android.server.inputmethod.TestUtils.createFakeSubtypes; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.os.Parcel; +import android.os.Parcelable; +import android.view.inputmethod.InputMethodInfo; + +import androidx.annotation.NonNull; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.Objects; + +public final class InputMethodInfoUtilsTest { + + @Test + public void testMarshalSameObject() { + final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3)); + final byte[] buf = InputMethodInfoUtils.marshal(imi); + + assertArrayEquals("The same value must be returned when called multiple times", + buf, InputMethodInfoUtils.marshal(imi)); + assertArrayEquals("The same value must be returned when called multiple times", + buf, InputMethodInfoUtils.marshal(imi)); + } + + @Test + public void testMarshalDifferentObjects() { + final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(0)); + + assertFalse("Different inputs must yield different byte patterns", Arrays.equals( + InputMethodInfoUtils.marshal(imi1), InputMethodInfoUtils.marshal(imi2))); + } + + @NonNull + private static <T> T readTypedObject(byte[] data, @NonNull Parcelable.Creator<T> creator) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + parcel.unmarshall(data, 0, data.length); + parcel.setDataPosition(0); + return Objects.requireNonNull(parcel.readTypedObject(creator)); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } + + @Test + public void testUnmarshalSameObject() { + final var imi = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3)); + final var cloned = readTypedObject(InputMethodInfoUtils.marshal(imi), + InputMethodInfo.CREATOR); + assertEquals(imi.getPackageName(), cloned.getPackageName()); + assertEquals(imi.getSubtypeCount(), cloned.getSubtypeCount()); + } +} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index b4cf79941c33..3b25cb13e66c 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +46,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.view.InputChannel; import android.view.inputmethod.EditorInfo; import android.window.ImeOnBackInvokedDispatcher; @@ -53,6 +55,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.compat.IPlatformCompat; import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.IInputMethodClient; +import com.android.internal.inputmethod.IInputMethodSession; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputBindResult; @@ -104,6 +107,7 @@ public class InputMethodManagerServiceTestBase { @Mock protected UserManagerInternal mMockUserManagerInternal; @Mock protected InputMethodBindingController mMockInputMethodBindingController; @Mock protected IInputMethodClient mMockInputMethodClient; + @Mock protected IInputMethodSession mMockInputMethodSession; @Mock protected IBinder mWindowToken; @Mock protected IRemoteInputConnection mMockRemoteInputConnection; @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection; @@ -123,6 +127,7 @@ public class InputMethodManagerServiceTestBase { protected IInputMethodInvoker mMockInputMethodInvoker; protected InputMethodManagerService mInputMethodManagerService; protected ServiceThread mServiceThread; + protected ServiceThread mPackageMonitorThread; protected boolean mIsLargeScreen; private InputManagerGlobal.TestSession mInputManagerGlobalSession; @@ -218,11 +223,18 @@ public class InputMethodManagerServiceTestBase { mServiceThread = new ServiceThread( - "TestServiceThread", - Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */ - false); - mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread, - mMockInputMethodBindingController); + "immstest1", + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); + mPackageMonitorThread = + new ServiceThread( + "immstest2", + Process.THREAD_PRIORITY_FOREGROUND, + true /* allowIo */); + mInputMethodManagerService = new InputMethodManagerService(mContext, + InputMethodManagerService.shouldEnableExperimentalConcurrentMultiUserMode(mContext), + mServiceThread, mPackageMonitorThread, + unusedUserId -> mMockInputMethodBindingController); spyOn(mInputMethodManagerService); // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of @@ -246,6 +258,7 @@ public class InputMethodManagerServiceTestBase { // Call InputMethodManagerService#addClient() as a preparation to start interacting with it. mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0); + createSessionForClient(mMockInputMethodClient); } @After @@ -254,6 +267,10 @@ public class InputMethodManagerServiceTestBase { mInputMethodManagerService.mInputMethodDeviceConfigs.destroy(); } + if (mPackageMonitorThread != null) { + mPackageMonitorThread.quitSafely(); + } + if (mServiceThread != null) { mServiceThread.quitSafely(); } @@ -295,4 +312,13 @@ public class InputMethodManagerServiceTestBase { .hideSoftInput(any() /* hideInputToken */, notNull() /* statsToken */, anyInt() /* flags */, any() /* resultReceiver */); } + + protected void createSessionForClient(IInputMethodClient client) { + synchronized (ImfLock.class) { + ClientState cs = mInputMethodManagerService.getClientStateLocked(client); + cs.mCurSession = new InputMethodManagerService.SessionState(cs, + mMockInputMethodInvoker, mMockInputMethodSession, mock( + InputChannel.class)); + } + } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java new file mode 100644 index 000000000000..be7042177a35 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodMapTest.java @@ -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.server.inputmethod; + +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID1; +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID2; +import static com.android.server.inputmethod.TestUtils.TEST_IME_ID3; +import static com.android.server.inputmethod.TestUtils.createFakeInputMethodInfo; +import static com.android.server.inputmethod.TestUtils.createFakeSubtypes; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.util.ArrayMap; +import android.view.inputmethod.InputMethodInfo; + +import androidx.annotation.NonNull; + +import org.junit.Test; + +public final class InputMethodMapTest { + + @NonNull + private static InputMethodMap toMap(InputMethodInfo... list) { + final ArrayMap<String, InputMethodInfo> map = new ArrayMap<>(); + for (var imi : list) { + map.put(imi.getId(), imi); + } + return InputMethodMap.of(map); + } + + @Test + public void testAreSameSameObject() { + final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3)); + final var map = toMap(imi1, imi2); + assertTrue("Must return true for the same instance", + InputMethodMap.areSame(map, map)); + } + + @Test + public void testAreSameEquivalentObject() { + final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3)); + assertTrue("Must return true for the equivalent instances", + InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1, imi2))); + + assertTrue("Must return true for the equivalent instances", + InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi2, imi1))); + } + + @Test + public void testAreSameDifferentKeys() { + final var imi1 = createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3)); + final var imi3 = createFakeInputMethodInfo(TEST_IME_ID3, createFakeSubtypes(3)); + assertFalse("Must return false if keys are different", + InputMethodMap.areSame(toMap(imi1), toMap(imi1, imi2))); + assertFalse("Must return false if keys are different", + InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1))); + assertFalse("Must return false if keys are different", + InputMethodMap.areSame(toMap(imi1, imi2), toMap(imi1, imi3))); + } + + @Test + public void testAreSameDifferentValues() { + final var imi1_without_subtypes = + createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(0)); + final var imi1_with_subtypes = + createFakeInputMethodInfo(TEST_IME_ID1, createFakeSubtypes(3)); + final var imi2 = createFakeInputMethodInfo(TEST_IME_ID2, createFakeSubtypes(3)); + assertFalse("Must return false if values are different", + InputMethodMap.areSame(toMap(imi1_without_subtypes), toMap(imi1_with_subtypes))); + assertFalse("Must return false if values are different", + InputMethodMap.areSame( + toMap(imi1_without_subtypes, imi2), + toMap(imi1_with_subtypes, imi2))); + } +} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java new file mode 100644 index 000000000000..c51ff87fdae5 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/TestUtils.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.inputmethod; + +import android.content.ComponentName; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodSubtype; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.Objects; + +public final class TestUtils { + /** + * {@link ComponentName} for fake {@link InputMethodInfo}. + */ + @NonNull + public static final ComponentName TEST_IME_ID1 = Objects.requireNonNull( + ComponentName.unflattenFromString("com.android.test.testime1/.InputMethod")); + + /** + * {@link ComponentName} for fake {@link InputMethodInfo}. + */ + @NonNull + public static final ComponentName TEST_IME_ID2 = Objects.requireNonNull( + ComponentName.unflattenFromString("com.android.test.testime2/.InputMethod")); + + /** + * {@link ComponentName} for fake {@link InputMethodInfo}. + */ + @NonNull + public static final ComponentName TEST_IME_ID3 = Objects.requireNonNull( + ComponentName.unflattenFromString("com.android.test.testime3/.InputMethod")); + + /** + * Creates a list of fake {@link InputMethodSubtype} for unit testing for the given number. + * + * @param count The number of fake {@link InputMethodSubtype} objects + * @return The list of fake {@link InputMethodSubtype} objects + */ + @NonNull + public static ArrayList<InputMethodSubtype> createFakeSubtypes(int count) { + final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(count); + for (int i = 0; i < count; ++i) { + subtypes.add( + new InputMethodSubtype.InputMethodSubtypeBuilder() + .setSubtypeId(i + 0x100) + .setLanguageTag("en-US") + .setSubtypeNameOverride("TestSubtype" + i) + .build()); + } + return subtypes; + } + + /** + * Creates a fake {@link InputMethodInfo} for unit testing. + * + * @param componentName {@link ComponentName} of the fake {@link InputMethodInfo} + * @param subtypes A list of (fake) {@link InputMethodSubtype} + * @return a fake {@link InputMethodInfo} object + */ + @NonNull + public static InputMethodInfo createFakeInputMethodInfo( + @NonNull ComponentName componentName, @NonNull ArrayList<InputMethodSubtype> subtypes) { + final ApplicationInfo ai = new ApplicationInfo(); + ai.packageName = componentName.getPackageName(); + ai.enabled = true; + + final ServiceInfo si = new ServiceInfo(); + si.applicationInfo = ai; + si.enabled = true; + si.packageName = componentName.getPackageName(); + si.name = componentName.getClassName(); + si.exported = true; + si.nonLocalizedLabel = "Fake Label"; + + final ResolveInfo ri = new ResolveInfo(); + ri.serviceInfo = si; + + return new InputMethodInfo(ri, false /* isAuxIme */, null /* settingsActivity */, + subtypes, 0 /* isDefaultResId */, false /* forceDefault */); + } +} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java index a15b17042174..c3a87dafe7ce 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/UserDataRepositoryTest.java @@ -18,6 +18,7 @@ package com.android.server.inputmethod; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -38,6 +39,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; +import java.util.function.IntFunction; // This test is designed to run on both device and host (Ravenwood) side. public final class UserDataRepositoryTest { @@ -51,19 +53,34 @@ public final class UserDataRepositoryTest { @Mock private UserManagerInternal mMockUserManagerInternal; + @Mock + private InputMethodManagerService mMockInputMethodManagerService; + private Handler mHandler; + private IntFunction<InputMethodBindingController> mBindingControllerFactory; + @Before public void setUp() { MockitoAnnotations.initMocks(this); mHandler = new Handler(Looper.getMainLooper()); + mBindingControllerFactory = new IntFunction<InputMethodBindingController>() { + + @Override + public InputMethodBindingController apply(int userId) { + return new InputMethodBindingController(userId, mMockInputMethodManagerService); + } + }; } @Test public void testUserDataRepository_addsNewUserInfoOnUserCreatedEvent() { // Create UserDataRepository and capture the user lifecycle listener final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class); - final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal); + final var bindingControllerFactorySpy = spy(mBindingControllerFactory); + final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, + bindingControllerFactorySpy); + verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture()); final var listener = captor.getValue(); @@ -77,14 +94,20 @@ public final class UserDataRepositoryTest { // Assert UserDataRepository contains the expected UserData final var allUserData = collectUserData(repository); assertThat(allUserData).hasSize(1); - assertThat(allUserData.get(0).mUserId).isEqualTo(userInfo.id); + assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID); + + // Assert UserDataRepository called the InputMethodBindingController creator function. + verify(bindingControllerFactorySpy).apply(ANY_USER_ID); + assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID); } @Test public void testUserDataRepository_removesUserInfoOnUserRemovedEvent() { // Create UserDataRepository and capture the user lifecycle listener final var captor = ArgumentCaptor.forClass(UserManagerInternal.UserLifecycleListener.class); - final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal); + final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, + userId -> new InputMethodBindingController(userId, mMockInputMethodManagerService)); + verify(mMockUserManagerInternal, times(1)).addUserLifecycleListener(captor.capture()); final var listener = captor.getValue(); @@ -104,7 +127,8 @@ public final class UserDataRepositoryTest { @Test public void testGetOrCreate() { - final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal); + final var repository = new UserDataRepository(mHandler, mMockUserManagerInternal, + mBindingControllerFactory); synchronized (ImfLock.class) { final var userData = repository.getOrCreate(ANY_USER_ID); @@ -114,6 +138,9 @@ public final class UserDataRepositoryTest { final var allUserData = collectUserData(repository); assertThat(allUserData).hasSize(1); assertThat(allUserData.get(0).mUserId).isEqualTo(ANY_USER_ID); + + // Assert UserDataRepository called the InputMethodBindingController creator function. + assertThat(allUserData.get(0).mBindingController.mUserId).isEqualTo(ANY_USER_ID); } private List<UserDataRepository.UserData> collectUserData(UserDataRepository repository) { diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp index ea7bb8b4a1d1..a738acb299c1 100644 --- a/services/tests/PackageManagerServiceTests/server/Android.bp +++ b/services/tests/PackageManagerServiceTests/server/Android.bp @@ -105,6 +105,7 @@ android_test { ":PackageParserTestApp5", ":PackageParserTestApp6", ":PackageParserTestApp7", + ":PackageParserTestApp8", ], resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"], diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index a0e0e1ef36ee..5da202f109d4 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -101,6 +101,7 @@ import com.android.internal.pm.pkg.component.ParsedServiceImpl; import com.android.internal.pm.pkg.component.ParsedUsesPermission; import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl; import com.android.internal.pm.pkg.parsing.ParsingPackage; +import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; import com.android.internal.util.ArrayUtils; import com.android.server.pm.parsing.PackageCacher; import com.android.server.pm.parsing.PackageInfoUtils; @@ -126,6 +127,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -154,6 +156,7 @@ public class PackageParserTest { private static final String TEST_APP5_APK = "PackageParserTestApp5.apk"; private static final String TEST_APP6_APK = "PackageParserTestApp6.apk"; private static final String TEST_APP7_APK = "PackageParserTestApp7.apk"; + private static final String TEST_APP8_APK = "PackageParserTestApp8.apk"; private static final String PACKAGE_NAME = "com.android.servicestests.apps.packageparserapp"; @Before @@ -814,6 +817,39 @@ public class PackageParserTest { } } + @Test + @RequiresFlagsEnabled(android.content.res.Flags.FLAG_MANIFEST_FLAGGING) + public void testParseWithFeatureFlagAttributes() throws Exception { + final File testFile = extractFile(TEST_APP8_APK); + try (PackageParser2 parser = new TestPackageParser2()) { + Map<String, Boolean> flagValues = new HashMap<>(); + flagValues.put("my.flag1", true); + flagValues.put("my.flag2", false); + flagValues.put("my.flag3", false); + flagValues.put("my.flag4", true); + ParsingPackageUtils.getAconfigFlags().addFlagValuesForTesting(flagValues); + + // The manifest has: + // <permission android:name="PERM1" android:featureFlag="my.flag1 " /> + // <permission android:name="PERM2" android:featureFlag=" !my.flag2" /> + // <permission android:name="PERM3" android:featureFlag="my.flag3" /> + // <permission android:name="PERM4" android:featureFlag="!my.flag4" /> + // <permission android:name="PERM5" android:featureFlag="unknown.flag" /> + // Therefore with the above flag values, only PERM1 and PERM2 should be present. + + final ParsedPackage pkg = parser.parsePackage(testFile, 0, false); + List<String> permissionNames = + pkg.getPermissions().stream().map(ParsedComponent::getName).toList(); + assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM1"); + assertThat(permissionNames).contains(PACKAGE_NAME + ".PERM2"); + assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM3"); + assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM4"); + assertThat(permissionNames).doesNotContain(PACKAGE_NAME + ".PERM5"); + } finally { + testFile.delete(); + } + } + /** * A subclass of package parser that adds a "cache_" prefix to the package name for the cached * results. This is used by tests to tell if a ParsedPackage is generated from the cache or not. diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java index 9e11fa2f0bdf..e545a49d3139 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java @@ -71,7 +71,7 @@ public class UserDataPreparerTest { @Mock private Installer mInstaller; - private Object mInstallLock; + private PackageManagerTracedLock mInstallLock; @Before public void setup() { @@ -79,7 +79,7 @@ public class UserDataPreparerTest { TEST_USER.serialNumber = TEST_USER_SERIAL; Context ctx = InstrumentationRegistry.getContext(); FileUtils.deleteContents(ctx.getCacheDir()); - mInstallLock = new Object(); + mInstallLock = new PackageManagerTracedLock(); MockitoAnnotations.initMocks(this); mUserDataPreparer = new TestUserDataPreparer(mInstaller, mInstallLock, mContextMock, ctx.getCacheDir()); @@ -238,8 +238,8 @@ public class UserDataPreparerTest { private static class TestUserDataPreparer extends UserDataPreparer { File testDir; - TestUserDataPreparer(Installer installer, Object installLock, Context context, - File testDir) { + TestUserDataPreparer(Installer installer, PackageManagerTracedLock installLock, + Context context, File testDir) { super(installer, installLock, context); this.testDir = testDir; } 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/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 64253e10bd21..3628a57f2375 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -1030,7 +1030,7 @@ public class AutomaticBrightnessControllerTest { } @Test - public void testBrightnessBasedOnLastObservedLux() throws Exception { + public void testBrightnessBasedOnLastUsedLux() throws Exception { ArgumentCaptor<SensorEventListener> listenerCaptor = ArgumentCaptor.forClass(SensorEventListener.class); verify(mSensorManager).registerListener(listenerCaptor.capture(), eq(mLightSensor), @@ -1054,7 +1054,7 @@ public class AutomaticBrightnessControllerTest { /* userChanged= */ false, DisplayPowerRequest.POLICY_BRIGHT, Display.STATE_ON, /* shouldResetShortTermModel= */ true); assertEquals(normalizedBrightness, - mController.getAutomaticScreenBrightnessBasedOnLastObservedLux( + mController.getAutomaticScreenBrightnessBasedOnLastUsedLux( /* brightnessEvent= */ null), EPSILON); } @@ -1090,7 +1090,7 @@ public class AutomaticBrightnessControllerTest { mController.getAutomaticScreenBrightness( /* brightnessEvent= */ null), EPSILON); assertEquals(normalizedBrightness * DOZE_SCALE_FACTOR, - mController.getAutomaticScreenBrightnessBasedOnLastObservedLux( + mController.getAutomaticScreenBrightnessBasedOnLastUsedLux( /* brightnessEvent= */ null), EPSILON); } @@ -1128,7 +1128,7 @@ public class AutomaticBrightnessControllerTest { assertEquals(normalizedBrightness, mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON); assertEquals(normalizedBrightness, - mController.getAutomaticScreenBrightnessBasedOnLastObservedLux( + mController.getAutomaticScreenBrightnessBasedOnLastUsedLux( /* brightnessEvent= */ null), EPSILON); } @@ -1163,7 +1163,7 @@ public class AutomaticBrightnessControllerTest { assertEquals(normalizedBrightness, mController.getAutomaticScreenBrightness(/* brightnessEvent= */ null), EPSILON); assertEquals(normalizedBrightness, - mController.getAutomaticScreenBrightnessBasedOnLastObservedLux( + mController.getAutomaticScreenBrightnessBasedOnLastUsedLux( /* brightnessEvent= */ null), EPSILON); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index a0a611ff4eb1..46d08b0ce018 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -21,7 +21,6 @@ import static com.android.internal.display.BrightnessSynchronizer.brightnessIntT import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT; import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE; import static com.android.server.display.config.SensorData.TEMPERATURE_TYPE_SKIN; -import static com.android.server.display.config.SensorData.SupportedMode; import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat; import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat; @@ -58,6 +57,7 @@ import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HysteresisLevels; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.RefreshRateData; +import com.android.server.display.config.SupportedModeData; import com.android.server.display.config.ThermalStatus; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.feature.flags.Flags; @@ -613,7 +613,7 @@ public final class DisplayDeviceConfigTest { assertEquals(mDisplayDeviceConfig.getProximitySensor().minRefreshRate, 60, SMALL_DELTA); assertEquals(mDisplayDeviceConfig.getProximitySensor().maxRefreshRate, 90, SMALL_DELTA); assertThat(mDisplayDeviceConfig.getProximitySensor().supportedModes).hasSize(2); - SupportedMode mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0); + SupportedModeData mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(0); assertEquals(mode.refreshRate, 60, SMALL_DELTA); assertEquals(mode.vsyncRate, 65, SMALL_DELTA); mode = mDisplayDeviceConfig.getProximitySensor().supportedModes.get(1); @@ -933,6 +933,21 @@ public final class DisplayDeviceConfigTest { assertEquals(0.2f, mDisplayDeviceConfig.getNitsFromBacklight(0.0f), ZERO_DELTA); } + @Test + public void testLowPowerSupportedModesFromConfigFile() throws IOException { + setupDisplayDeviceConfigFromDisplayConfigFile(); + + RefreshRateData refreshRateData = mDisplayDeviceConfig.getRefreshRateData(); + assertNotNull(refreshRateData); + assertThat(refreshRateData.lowPowerSupportedModes).hasSize(2); + SupportedModeData supportedModeData = refreshRateData.lowPowerSupportedModes.get(0); + assertThat(supportedModeData.refreshRate).isEqualTo(60); + assertThat(supportedModeData.vsyncRate).isEqualTo(60); + supportedModeData = refreshRateData.lowPowerSupportedModes.get(1); + assertThat(supportedModeData.refreshRate).isEqualTo(60); + assertThat(supportedModeData.vsyncRate).isEqualTo(120); + } + private String getValidLuxThrottling() { return "<luxThrottling>\n" + " <brightnessLimitMap>\n" @@ -1089,6 +1104,19 @@ public final class DisplayDeviceConfigTest { + "</proxSensor>\n"; } + private String getLowPowerConfig() { + return "<lowPowerSupportedModes>\n" + + " <point>\n" + + " <first>60</first>\n" + + " <second>60</second>\n" + + " </point>\n" + + " <point>\n" + + " <first>60</first>\n" + + " <second>120</second>\n" + + " </point>\n" + + "</lowPowerSupportedModes>\n"; + } + private String getHdrBrightnessConfig() { return "<hdrBrightnessConfig>\n" + " <brightnessMap>\n" @@ -1620,6 +1648,7 @@ public final class DisplayDeviceConfigTest { + "</displayBrightnessPoint>\n" + "</blockingZoneThreshold>\n" + "</higherBlockingZoneConfigs>\n" + + getLowPowerConfig() + "</refreshRate>\n" + "<screenOffBrightnessSensorValueToLux>\n" + "<item>-1</item>\n" 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..8844e6cc3a2c 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; } @@ -709,6 +709,64 @@ public class DisplayManagerServiceTest { assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0); } + @Test + public void testCreateVirtualDisplayOwnFocus_checkDisplayDeviceInfo() throws RemoteException { + DisplayManagerService displayManager = + new DisplayManagerService(mContext, mBasicInjector); + registerDefaultDisplays(displayManager); + + // This is effectively the DisplayManager service published to ServiceManager. + DisplayManagerService.BinderService bs = displayManager.new BinderService(); + + final String uniqueId = "uniqueId --- Own Focus Test -- checkDisplayDeviceInfo"; + float refreshRate = 60.0f; + int width = 600; + int height = 800; + int dpi = 320; + int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS; + + when(mContext.checkCallingPermission(ADD_TRUSTED_DISPLAY)).thenReturn( + PackageManager.PERMISSION_GRANTED); + when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); + final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder( + VIRTUAL_DISPLAY_NAME, width, height, dpi); + builder.setFlags(flags); + builder.setUniqueId(uniqueId); + builder.setRequestedRefreshRate(refreshRate); + + // Create a virtual display in its own display group. + final VirtualDisplayConfig ownerDisplayConfig = builder.build(); + int displayId = bs.createVirtualDisplay(ownerDisplayConfig, /* callback= */ mMockAppToken, + /* projection= */ null, PACKAGE_NAME); + verify(mMockProjectionService, never()).setContentRecordingSession(any(), + nullable(IMediaProjection.class)); + + DisplayInfo displayInfo = bs.getDisplayInfo(displayId); + assertNotNull(displayInfo); + assertTrue((displayInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0); + final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId( + PACKAGE_NAME, Process.myUid(), ownerDisplayConfig); + assertEquals(displayInfo.uniqueId, displayUniqueId); + assertEquals(displayInfo.name, VIRTUAL_DISPLAY_NAME); + assertEquals(displayInfo.ownerPackageName, PACKAGE_NAME); + assertEquals(displayInfo.getRefreshRate(), refreshRate, 0.1f); + + performTraversalInternal(displayManager); + + // Flush the handler. + displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0); + + DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId); + assertNotNull(ddi); + assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) == 0); + assertEquals(ddi.width, width); + assertEquals(ddi.height, height); + assertEquals(ddi.name, displayInfo.name); + assertEquals(ddi.ownerPackageName, displayInfo.ownerPackageName); + assertEquals(ddi.uniqueId, displayInfo.uniqueId); + assertEquals(ddi.renderFrameRate, displayInfo.getRefreshRate(), 0.1f); + } + /** * Tests that the virtual display is created along-side the default display. */ 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 78ebdb14f8d1..e5685c7f4f43 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1776,7 +1776,7 @@ public final class DisplayPowerControllerTest { float brightness = 0.277f; when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); when(mHolder.automaticBrightnessController - .getAutomaticScreenBrightnessBasedOnLastObservedLux(any(BrightnessEvent.class))) + .getAutomaticScreenBrightnessBasedOnLastUsedLux(any(BrightnessEvent.class))) .thenReturn(brightness); when(mHolder.hbmController.getCurrentBrightnessMax()) .thenReturn(PowerManager.BRIGHTNESS_MAX); @@ -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 @@ -1840,7 +1841,7 @@ public final class DisplayPowerControllerTest { float brightness = 0.277f; when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f); when(mHolder.automaticBrightnessController - .getAutomaticScreenBrightnessBasedOnLastObservedLux(any(BrightnessEvent.class))) + .getAutomaticScreenBrightnessBasedOnLastUsedLux(any(BrightnessEvent.class))) .thenReturn(brightness); when(mHolder.hbmController.getCurrentBrightnessMax()) .thenReturn(PowerManager.BRIGHTNESS_MAX); diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java index c01b15c17483..81e6cc3f546b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java @@ -18,12 +18,14 @@ package com.android.server.display; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.display.IVirtualDisplayCallback; import android.hardware.display.VirtualDisplayConfig; import android.os.IBinder; +import android.os.Process; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -88,6 +90,25 @@ public class VirtualDisplayAdapterTest { } @Test + public void testCreatesVirtualDisplay_checkGeneratedDisplayUniqueIdPrefix() { + VirtualDisplayConfig config = new VirtualDisplayConfig.Builder("test", /* width= */ 1, + /* height= */ 1, /* densityDpi= */ 1).build(); + + final String packageName = "testpackage"; + final String displayUniqueId = VirtualDisplayAdapter.generateDisplayUniqueId( + packageName, Process.myUid(), config); + + DisplayDevice result = mVirtualDisplayAdapter.createVirtualDisplayLocked( + mMockCallback, /* projection= */ null, /* ownerUid= */ 10, + packageName, displayUniqueId, /* surface= */ null, /* flags= */ 0, config); + + assertNotNull(result); + + final String uniqueId = result.getUniqueId(); + assertTrue(uniqueId.startsWith(VirtualDisplayAdapter.UNIQUE_ID_PREFIX + packageName)); + } + + @Test public void testDoesNotCreateVirtualDisplayForSameCallback() { VirtualDisplayConfig config1 = new VirtualDisplayConfig.Builder("test", /* width= */ 1, /* height= */ 1, /* densityDpi= */ 1).build(); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index 13a1445bce76..c5105e78d0b3 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -32,7 +32,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Resources; +import android.hardware.SensorManager; import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest; +import android.os.Handler; import android.os.HandlerExecutor; import android.os.PowerManager; import android.view.Display; @@ -41,7 +43,11 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.display.AutomaticBrightnessController; +import com.android.server.display.BrightnessMappingStrategy; import com.android.server.display.BrightnessSetting; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStrategy; +import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy; @@ -75,14 +81,14 @@ public final class DisplayBrightnessControllerTest { @Mock private HandlerExecutor mBrightnessChangeExecutor; - private final DisplayBrightnessController.Injector mInjector = new - DisplayBrightnessController.Injector() { - @Override - DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector( - Context context, int displayId, DisplayManagerFlags flags) { - return mDisplayBrightnessStrategySelector; - } - }; + private final DisplayBrightnessController.Injector mInjector = + new DisplayBrightnessController.Injector() { + @Override + DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector( + Context context, int displayId, DisplayManagerFlags flags) { + return mDisplayBrightnessStrategySelector; + } + }; private DisplayBrightnessController mDisplayBrightnessController; @@ -196,10 +202,9 @@ public final class DisplayBrightnessControllerTest { pendingScreenBrightness, /* delta= */ 0.0f); assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(), pendingScreenBrightness, /* delta= */ 0.0f); - verify(mBrightnessChangeExecutor, times(2)) - .execute(mOnBrightnessChangeRunnable); - verify(temporaryBrightnessStrategy, times(2)) - .setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT); + verify(mBrightnessChangeExecutor, times(2)).execute(mOnBrightnessChangeRunnable); + verify(temporaryBrightnessStrategy, times(2)).setTemporaryScreenBrightness( + PowerManager.BRIGHTNESS_INVALID_FLOAT); assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(), PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f); } @@ -253,8 +258,7 @@ public final class DisplayBrightnessControllerTest { // Sets the appropriate value when valid, and not equal to the current brightness float brightnessValue = 0.3f; mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue); - assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue, - 0.0f); + assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue, 0.0f); verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable); verify(mBrightnessSetting).setBrightness(brightnessValue); @@ -265,8 +269,7 @@ public final class DisplayBrightnessControllerTest { // Does nothing if the value is same as the current brightness brightnessValue = 0.2f; mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue); - verify(mBrightnessChangeExecutor, times(2)) - .execute(mOnBrightnessChangeRunnable); + verify(mBrightnessChangeExecutor, times(2)).execute(mOnBrightnessChangeRunnable); mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue); verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting); } @@ -283,14 +286,22 @@ public final class DisplayBrightnessControllerTest { assertEquals(-1f, mDisplayBrightnessController.convertToAdjustedNits(brightness), /* delta= */ 0); - AutomaticBrightnessController automaticBrightnessController = - mock(AutomaticBrightnessController.class); + AutomaticBrightnessController automaticBrightnessController = mock( + AutomaticBrightnessController.class); + + AutomaticBrightnessStrategy automaticBrightnessStrategy = + mock(AutomaticBrightnessStrategy.class); + when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy()) + .thenReturn(automaticBrightnessStrategy); + when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); - when(automaticBrightnessController.convertToAdjustedNits(brightness)) - .thenReturn(adjustedNits); + when(automaticBrightnessController.convertToAdjustedNits(brightness)).thenReturn( + adjustedNits); mDisplayBrightnessController.setAutomaticBrightnessController( automaticBrightnessController); + verify(automaticBrightnessStrategy) + .setAutomaticBrightnessController(automaticBrightnessController); assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0); assertEquals(adjustedNits, mDisplayBrightnessController.convertToAdjustedNits(brightness), /* delta= */ 0); @@ -305,12 +316,17 @@ public final class DisplayBrightnessControllerTest { assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT, mDisplayBrightnessController.getBrightnessFromNits(nits), /* delta= */ 0); - AutomaticBrightnessController automaticBrightnessController = - mock(AutomaticBrightnessController.class); + AutomaticBrightnessController automaticBrightnessController = mock( + AutomaticBrightnessController.class); + AutomaticBrightnessStrategy automaticBrightnessStrategy = + mock(AutomaticBrightnessStrategy.class); + when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy()) + .thenReturn(automaticBrightnessStrategy); when(automaticBrightnessController.getBrightnessFromNits(nits)).thenReturn(brightness); mDisplayBrightnessController.setAutomaticBrightnessController( automaticBrightnessController); - + verify(automaticBrightnessStrategy) + .setAutomaticBrightnessController(automaticBrightnessController); assertEquals(brightness, mDisplayBrightnessController.getBrightnessFromNits(nits), /* delta= */ 0); } @@ -331,8 +347,12 @@ public final class DisplayBrightnessControllerTest { // value float nits = 200f; float brightness = 0.3f; - AutomaticBrightnessController automaticBrightnessController = - mock(AutomaticBrightnessController.class); + AutomaticBrightnessController automaticBrightnessController = mock( + AutomaticBrightnessController.class); + AutomaticBrightnessStrategy automaticBrightnessStrategy = + mock(AutomaticBrightnessStrategy.class); + when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy()) + .thenReturn(automaticBrightnessStrategy); when(automaticBrightnessController.getBrightnessFromNits(nits)).thenReturn(brightness); when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits); mDisplayBrightnessController.setAutomaticBrightnessController( @@ -375,12 +395,18 @@ public final class DisplayBrightnessControllerTest { float nits1 = 200f; float brightnessValue2 = 0.5f; float nits2 = 300f; - AutomaticBrightnessController automaticBrightnessController = - mock(AutomaticBrightnessController.class); + AutomaticBrightnessController automaticBrightnessController = mock( + AutomaticBrightnessController.class); when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1); when(automaticBrightnessController.convertToNits(brightnessValue2)).thenReturn(nits2); + AutomaticBrightnessStrategy automaticBrightnessStrategy = + mock(AutomaticBrightnessStrategy.class); + when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy()) + .thenReturn(automaticBrightnessStrategy); mDisplayBrightnessController.setAutomaticBrightnessController( automaticBrightnessController); + verify(automaticBrightnessStrategy) + .setAutomaticBrightnessController(automaticBrightnessController); mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */); verify(mBrightnessSetting).setUserSerial(1); @@ -399,8 +425,8 @@ public final class DisplayBrightnessControllerTest { OffloadBrightnessStrategy offloadBrightnessStrategy = mock(OffloadBrightnessStrategy.class); when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn( offloadBrightnessStrategy); - boolean brightnessUpdated = - mDisplayBrightnessController.setBrightnessFromOffload(brightness); + boolean brightnessUpdated = mDisplayBrightnessController.setBrightnessFromOffload( + brightness); verify(offloadBrightnessStrategy).setOffloadScreenBrightness(brightness); assertTrue(brightnessUpdated); } @@ -409,8 +435,8 @@ public final class DisplayBrightnessControllerTest { public void setBrightnessFromOffload_OffloadStrategyNull() { float brightness = 0.4f; when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn(null); - boolean brightnessUpdated = - mDisplayBrightnessController.setBrightnessFromOffload(brightness); + boolean brightnessUpdated = mDisplayBrightnessController.setBrightnessFromOffload( + brightness); assertFalse(brightnessUpdated); } @@ -421,9 +447,42 @@ public final class DisplayBrightnessControllerTest { when(offloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(brightness); when(mDisplayBrightnessStrategySelector.getOffloadBrightnessStrategy()).thenReturn( offloadBrightnessStrategy); - boolean brightnessUpdated = - mDisplayBrightnessController.setBrightnessFromOffload(brightness); + boolean brightnessUpdated = mDisplayBrightnessController.setBrightnessFromOffload( + brightness); verify(offloadBrightnessStrategy, never()).setOffloadScreenBrightness(brightness); assertFalse(brightnessUpdated); } + + @Test + public void setupAutoBrightness_setsAutomaticStrategyAndAutoBrightnessFallbackStrategy() { + // Setup the strategy mocks + AutomaticBrightnessStrategy automaticBrightnessStrategy = mock( + AutomaticBrightnessStrategy.class); + AutoBrightnessFallbackStrategy autoBrightnessFallbackStrategy = mock( + AutoBrightnessFallbackStrategy.class); + when(mDisplayBrightnessStrategySelector.getAutomaticBrightnessStrategy()) + .thenReturn(automaticBrightnessStrategy); + when(mDisplayBrightnessStrategySelector.getAutoBrightnessFallbackStrategy()) + .thenReturn(autoBrightnessFallbackStrategy); + + // Setup the argument mocks + AutomaticBrightnessController automaticBrightnessController = mock( + AutomaticBrightnessController.class); + SensorManager sensorManager = mock(SensorManager.class); + DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class); + Handler handler = mock(Handler.class); + BrightnessMappingStrategy brightnessMappingStrategy = mock(BrightnessMappingStrategy.class); + boolean isEnabled = true; + int leadDisplayId = 2; + + mDisplayBrightnessController.setUpAutoBrightness(automaticBrightnessController, + sensorManager, displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled, + leadDisplayId); + assertEquals(automaticBrightnessController, + mDisplayBrightnessController.mAutomaticBrightnessController); + verify(automaticBrightnessStrategy).setAutomaticBrightnessController( + automaticBrightnessController); + verify(autoBrightnessFallbackStrategy).setupAutoBrightnessFallbackSensor(sensorManager, + displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled, leadDisplayId); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index f2706502d391..df9671235071 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -40,10 +40,13 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.R; import com.android.internal.util.test.FakeSettingsProvider; import com.android.internal.util.test.FakeSettingsProviderRule; +import com.android.server.display.brightness.strategy.AutoBrightnessFallbackStrategy; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy2; import com.android.server.display.brightness.strategy.BoostBrightnessStrategy; +import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy; import com.android.server.display.brightness.strategy.DozeBrightnessStrategy; +import com.android.server.display.brightness.strategy.FallbackBrightnessStrategy; import com.android.server.display.brightness.strategy.FollowerBrightnessStrategy; import com.android.server.display.brightness.strategy.InvalidBrightnessStrategy; import com.android.server.display.brightness.strategy.OffloadBrightnessStrategy; @@ -86,6 +89,10 @@ public final class DisplayBrightnessStrategySelectorTest { @Mock private OffloadBrightnessStrategy mOffloadBrightnessStrategy; @Mock + private AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy; + @Mock + private FallbackBrightnessStrategy mFallbackBrightnessStrategy; + @Mock private Resources mResources; @Mock private DisplayManagerFlags mDisplayManagerFlags; @@ -131,7 +138,7 @@ public final class DisplayBrightnessStrategySelectorTest { @Override AutomaticBrightnessStrategy getAutomaticBrightnessStrategy1(Context context, - int displayId) { + int displayId, DisplayManagerFlags displayManagerFlags) { return mAutomaticBrightnessStrategy; } @@ -146,6 +153,16 @@ public final class DisplayBrightnessStrategySelectorTest { DisplayManagerFlags displayManagerFlags) { return mOffloadBrightnessStrategy; } + + @Override + AutoBrightnessFallbackStrategy getAutoBrightnessFallbackStrategy() { + return mAutoBrightnessFallbackStrategy; + } + + @Override + FallbackBrightnessStrategy getFallbackBrightnessStrategy() { + return mFallbackBrightnessStrategy; + } }; @Rule @@ -187,8 +204,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( - new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false)), + new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, + 0.1f, false)), mDozeBrightnessModeStrategy); } @@ -309,6 +326,26 @@ public final class DisplayBrightnessStrategySelectorTest { } @Test + public void selectStrategy_selectsAutomaticFallbackStrategyWhenValid() { + when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true); + when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false); + when(mAutoBrightnessFallbackStrategy.isValid()).thenReturn(true); + assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( + new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, + 0.1f, false)), + mAutoBrightnessFallbackStrategy); + } + + @Test public void selectStrategyDoesNotSelectOffloadStrategyWhenFeatureFlagDisabled() { when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(false); mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, @@ -326,6 +363,25 @@ public final class DisplayBrightnessStrategySelectorTest { } @Test + public void selectStrategy_selectsFallbackStrategyAsAnUltimateFallback() { + when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(false); + when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false); + assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( + new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, + 0.1f, false)), + mFallbackBrightnessStrategy); + } + + @Test public void selectStrategyCallsPostProcessorForAllStrategies() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, @@ -341,23 +397,15 @@ public final class DisplayBrightnessStrategySelectorTest { StrategySelectionNotifyRequest strategySelectionNotifyRequest = new StrategySelectionNotifyRequest(displayPowerRequest, Display.STATE_ON, mFollowerBrightnessStrategy, 0.1f, - false, false); - verify(mInvalidBrightnessStrategy).strategySelectionPostProcessor( - eq(strategySelectionNotifyRequest)); - verify(mScreenOffBrightnessModeStrategy).strategySelectionPostProcessor( - eq(strategySelectionNotifyRequest)); - verify(mDozeBrightnessModeStrategy).strategySelectionPostProcessor( - eq(strategySelectionNotifyRequest)); - verify(mFollowerBrightnessStrategy).strategySelectionPostProcessor( - eq(strategySelectionNotifyRequest)); - verify(mBoostBrightnessStrategy).strategySelectionPostProcessor( - eq(strategySelectionNotifyRequest)); - verify(mOverrideBrightnessStrategy).strategySelectionPostProcessor( - eq(strategySelectionNotifyRequest)); - verify(mTemporaryBrightnessStrategy).strategySelectionPostProcessor( - eq(strategySelectionNotifyRequest)); - verify(mAutomaticBrightnessStrategy).strategySelectionPostProcessor( - eq(strategySelectionNotifyRequest)); + false, false, false); + + for (DisplayBrightnessStrategy displayBrightnessStrategy : + mDisplayBrightnessStrategySelector.mDisplayBrightnessStrategies) { + if (displayBrightnessStrategy != null) { + verify(displayBrightnessStrategy).strategySelectionPostProcessor( + eq(strategySelectionNotifyRequest)); + } + } } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java new file mode 100644 index 000000000000..7a6a91108282 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.strategy; + + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.hardware.display.DisplayManagerInternal; +import android.os.Handler; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.BrightnessMappingStrategy; +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.ScreenOffBrightnessSensorController; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.StrategyExecutionRequest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AutoBrightnessFallbackStrategyTest { + private AutoBrightnessFallbackStrategy mAutoBrightnessFallbackStrategy; + + @Mock + private Sensor mScreenOffBrightnessSensor; + + @Mock + private ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + mAutoBrightnessFallbackStrategy = new AutoBrightnessFallbackStrategy( + new AutoBrightnessFallbackStrategy.Injector() { + @Override + public Sensor getScreenOffBrightnessSensor(SensorManager sensorManager, + DisplayDeviceConfig displayDeviceConfig) { + return mScreenOffBrightnessSensor; + } + + @Override + public ScreenOffBrightnessSensorController + getScreenOffBrightnessSensorController(SensorManager sensorManager, + Sensor lightSensor, Handler handler, + ScreenOffBrightnessSensorController.Clock clock, + int[] sensorValueToLux, + BrightnessMappingStrategy brightnessMapper) { + return mScreenOffBrightnessSensorController; + } + }); + } + + @Test + public void testUpdateBrightnessWhenScreenDozeStateIsRequested() { + // Setup the argument mocks + SensorManager sensorManager = mock(SensorManager.class); + DisplayDeviceConfig displayDeviceConfig = mock(DisplayDeviceConfig.class); + Handler handler = mock(Handler.class); + BrightnessMappingStrategy brightnessMappingStrategy = mock(BrightnessMappingStrategy.class); + boolean isEnabled = true; + int leadDisplayId = 2; + + int[] sensorValueToLux = new int[]{50, 100}; + when(displayDeviceConfig.getScreenOffBrightnessSensorValueToLux()).thenReturn( + sensorValueToLux); + + mAutoBrightnessFallbackStrategy.setupAutoBrightnessFallbackSensor(sensorManager, + displayDeviceConfig, handler, brightnessMappingStrategy, isEnabled, leadDisplayId); + + assertEquals(mScreenOffBrightnessSensor, + mAutoBrightnessFallbackStrategy.mScreenOffBrightnessSensor); + assertEquals(mScreenOffBrightnessSensorController, + mAutoBrightnessFallbackStrategy.getScreenOffBrightnessSensorController()); + + DisplayManagerInternal.DisplayPowerRequest + displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest(); + float fallbackBrightness = 0.2f; + when(mScreenOffBrightnessSensorController.getAutomaticScreenBrightness()).thenReturn( + fallbackBrightness); + + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_SCREEN_OFF_BRIGHTNESS_SENSOR); + DisplayBrightnessState expectedDisplayBrightnessState = + new DisplayBrightnessState.Builder() + .setBrightness(fallbackBrightness) + .setBrightnessReason(brightnessReason) + .setSdrBrightness(fallbackBrightness) + .setDisplayBrightnessStrategyName(mAutoBrightnessFallbackStrategy.getName()) + .build(); + DisplayBrightnessState updatedDisplayBrightnessState = + mAutoBrightnessFallbackStrategy.updateBrightness( + new StrategyExecutionRequest(displayPowerRequest, 0.2f)); + assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); + } + +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java index fd43720b1c03..09f5bb60a6ad 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy2Test.java @@ -343,7 +343,7 @@ public class AutomaticBrightnessStrategy2Test { AutomaticBrightnessController.class); when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class))) .thenReturn(automaticScreenBrightness); - when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastObservedLux( + when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastUsedLux( any(BrightnessEvent.class))) .thenReturn(automaticScreenBrightness); mAutomaticBrightnessStrategy.setAutomaticBrightnessController( @@ -352,7 +352,7 @@ public class AutomaticBrightnessStrategy2Test { mAutomaticBrightnessStrategy.getAutomaticScreenBrightness( new BrightnessEvent(DISPLAY_ID)), 0.0f); assertEquals(automaticScreenBrightness, - mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastObservedLux( + mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastUsedLux( new BrightnessEvent(DISPLAY_ID)), 0.0f); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index 54f22687bc75..d19f47951df1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -18,8 +18,10 @@ package com.android.server.display.brightness.strategy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -45,6 +47,7 @@ import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.StrategyExecutionRequest; +import com.android.server.display.feature.DisplayManagerFlags; import org.junit.After; import org.junit.Before; @@ -64,6 +67,9 @@ public class AutomaticBrightnessStrategyTest { @Mock private AutomaticBrightnessController mAutomaticBrightnessController; + @Mock + private DisplayManagerFlags mDisplayManagerFlags; + private BrightnessConfiguration mBrightnessConfiguration; private float mDefaultScreenAutoBrightnessAdjustment; private Context mContext; @@ -80,7 +86,8 @@ public class AutomaticBrightnessStrategyTest { Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, Float.NaN); Settings.System.putFloat(mContext.getContentResolver(), Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, 0.5f); - mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID); + mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, DISPLAY_ID, + mDisplayManagerFlags); mBrightnessConfiguration = new BrightnessConfiguration.Builder( new float[]{0f, 1f}, new float[]{0, PowerManager.BRIGHTNESS_ON}).build(); @@ -247,6 +254,46 @@ public class AutomaticBrightnessStrategyTest { } @Test + public void testAutoBrightnessState_modeSwitch() { + // Setup the test + when(mDisplayManagerFlags.areAutoBrightnessModesEnabled()).thenReturn(true); + mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + boolean allowAutoBrightnessWhileDozing = false; + int brightnessReason = BrightnessReason.REASON_UNKNOWN; + float lastUserSetBrightness = 0.2f; + boolean userSetBrightnessChanged = true; + int policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + float pendingBrightnessAdjustment = 0.1f; + Settings.System.putFloat(mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, pendingBrightnessAdjustment); + mAutomaticBrightnessStrategy.updatePendingAutoBrightnessAdjustments(); + + // Validate no interaction when automaticBrightnessController is in idle mode + when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(true); + mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController, never()).switchMode(anyInt()); + + // Validate interaction when automaticBrightnessController is in non-idle mode, and display + // state is ON + when(mAutomaticBrightnessController.isInIdleMode()).thenReturn(false); + mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController).switchMode( + AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT); + + // Validate interaction when automaticBrightnessController is in non-idle mode, and display + // state is DOZE + mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_DOZE, + allowAutoBrightnessWhileDozing, brightnessReason, policy, lastUserSetBrightness, + userSetBrightnessChanged); + verify(mAutomaticBrightnessController).switchMode( + AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE); + } + + @Test public void accommodateUserBrightnessChangesWorksAsExpected() { // Verify the state if automaticBrightnessController is configured. assertFalse(mAutomaticBrightnessStrategy.isShortTermModelActive()); @@ -341,16 +388,16 @@ public class AutomaticBrightnessStrategyTest { AutomaticBrightnessController.class); when(automaticBrightnessController.getAutomaticScreenBrightness(any(BrightnessEvent.class))) .thenReturn(automaticScreenBrightness); - when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastObservedLux( + when(automaticBrightnessController.getAutomaticScreenBrightnessBasedOnLastUsedLux( any(BrightnessEvent.class))) .thenReturn(automaticScreenBrightness); mAutomaticBrightnessStrategy.setAutomaticBrightnessController( automaticBrightnessController); assertEquals(automaticScreenBrightness, mAutomaticBrightnessStrategy.getAutomaticScreenBrightness( - new BrightnessEvent(DISPLAY_ID)), 0.0f); + new BrightnessEvent(DISPLAY_ID), false), 0.0f); assertEquals(automaticScreenBrightness, - mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastObservedLux( + mAutomaticBrightnessStrategy.getAutomaticScreenBrightnessBasedOnLastUsedLux( new BrightnessEvent(DISPLAY_ID)), 0.0f); } @@ -390,7 +437,8 @@ public class AutomaticBrightnessStrategyTest { @Test public void testVerifyNoAutoBrightnessAdjustmentsArePopulatedForNonDefaultDisplay() { int newDisplayId = 1; - mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId); + mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy(mContext, newDisplayId, + mDisplayManagerFlags); mAutomaticBrightnessStrategy.putAutoBrightnessAdjustmentSetting(0.3f); assertEquals(0.5f, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f); @@ -413,8 +461,12 @@ public class AutomaticBrightnessStrategyTest { } @Test - public void isAutoBrightnessValid_returnsTrueWhenBrightnessIsValid() { + public void isAutoBrightnessValid_returnsTrueWhenBrightnessIsValid_adjustsAutoBrightness() + throws Settings.SettingNotFoundException { + float adjustment = 0.1f; mAutomaticBrightnessStrategy.setUseAutoBrightness(true); + when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()) + .thenReturn(0.1f); mAutomaticBrightnessStrategy.setAutoBrightnessState(Display.STATE_ON, true, BrightnessReason.REASON_UNKNOWN, DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT, 0.1f, @@ -422,6 +474,11 @@ public class AutomaticBrightnessStrategyTest { when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null)) .thenReturn(0.2f); assertTrue(mAutomaticBrightnessStrategy.isAutoBrightnessValid()); + assertEquals(adjustment, mAutomaticBrightnessStrategy.getAutoBrightnessAdjustment(), 0.0f); + assertEquals(adjustment, Settings.System.getFloatForUser( + mContext.getContentResolver(), + Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, + UserHandle.USER_CURRENT), 0.0f); } @Test @@ -429,8 +486,7 @@ public class AutomaticBrightnessStrategyTest { updateBrightness_constructsDisplayBrightnessState_withAdjustmentAutoAdjustmentFlag() { BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID); mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy( - mContext, DISPLAY_ID, displayId -> brightnessEvent); - new AutomaticBrightnessStrategy(mContext, DISPLAY_ID); + mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags); mAutomaticBrightnessStrategy.setAutomaticBrightnessController( mAutomaticBrightnessController); float brightness = 0.4f; @@ -439,6 +495,15 @@ public class AutomaticBrightnessStrategyTest { when(mAutomaticBrightnessController.getAutomaticScreenBrightness(brightnessEvent)) .thenReturn(brightness); + + // We do this to apply the automatic brightness adjustments + when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn( + 0.25f); + when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null)) + .thenReturn(brightness); + assertEquals(brightness, mAutomaticBrightnessStrategy + .getAutomaticScreenBrightness(null, false), 0.0f); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock(DisplayManagerInternal.DisplayPowerRequest.class); DisplayBrightnessState expectedDisplayBrightnessState = new DisplayBrightnessState.Builder() @@ -461,8 +526,7 @@ public class AutomaticBrightnessStrategyTest { updateBrightness_constructsDisplayBrightnessState_withAdjustmentTempAdjustmentFlag() { BrightnessEvent brightnessEvent = new BrightnessEvent(DISPLAY_ID); mAutomaticBrightnessStrategy = new AutomaticBrightnessStrategy( - mContext, DISPLAY_ID, displayId -> brightnessEvent); - new AutomaticBrightnessStrategy(mContext, DISPLAY_ID); + mContext, DISPLAY_ID, displayId -> brightnessEvent, mDisplayManagerFlags); mAutomaticBrightnessStrategy.setAutomaticBrightnessController( mAutomaticBrightnessController); float brightness = 0.4f; @@ -483,6 +547,12 @@ public class AutomaticBrightnessStrategyTest { when(mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment()).thenReturn( autoBrightnessAdjustment); + // We do this to apply the automatic brightness adjustments + when(mAutomaticBrightnessController.getAutomaticScreenBrightness(null)) + .thenReturn(brightness); + assertEquals(brightness, mAutomaticBrightnessStrategy + .getAutomaticScreenBrightness(null, false), 0.0f); + DisplayBrightnessState expectedDisplayBrightnessState = new DisplayBrightnessState.Builder() .setBrightness(brightness) .setSdrBrightness(brightness) diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java new file mode 100644 index 000000000000..c4767ae5172b --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display.brightness.strategy; + + +import static org.junit.Assert.assertEquals; + +import android.hardware.display.DisplayManagerInternal; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.display.DisplayBrightnessState; +import com.android.server.display.brightness.BrightnessReason; +import com.android.server.display.brightness.StrategyExecutionRequest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidJUnit4.class) + +public class FallbackBrightnessStrategyTest { + private FallbackBrightnessStrategy mFallbackBrightnessStrategy; + + @Before + public void before() { + mFallbackBrightnessStrategy = new FallbackBrightnessStrategy(); + } + + @Test + public void updateBrightness_currentBrightnessIsSet() { + DisplayManagerInternal.DisplayPowerRequest + displayPowerRequest = new DisplayManagerInternal.DisplayPowerRequest(); + float currentBrightness = 0.2f; + BrightnessReason brightnessReason = new BrightnessReason(); + brightnessReason.setReason(BrightnessReason.REASON_MANUAL); + DisplayBrightnessState expectedDisplayBrightnessState = + new DisplayBrightnessState.Builder() + .setBrightness(currentBrightness) + .setBrightnessReason(brightnessReason) + .setSdrBrightness(currentBrightness) + .setDisplayBrightnessStrategyName(mFallbackBrightnessStrategy.getName()) + .setShouldUpdateScreenBrightnessSetting(true) + .build(); + DisplayBrightnessState updatedDisplayBrightnessState = + mFallbackBrightnessStrategy.updateBrightness( + new StrategyExecutionRequest(displayPowerRequest, currentBrightness)); + assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java index a785300e98a3..27f87aae35bb 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayTransformManagerTest.java @@ -161,4 +161,20 @@ public class DisplayTransformManagerTest { .isEqualTo(Integer.toString(testPropertyValue)); } + @Test + public void daltonizer_defaultValues() { + synchronized (mDtm.mDaltonizerModeLock) { + assertThat(mDtm.mDaltonizerMode).isEqualTo(-1); + assertThat(mDtm.mDaltonizerLevel).isEqualTo(-1); + } + } + + @Test + public void setDaltonizerMode_newValues_valuesUpdated() { + mDtm.setDaltonizerMode(0, 0); + synchronized (mDtm.mDaltonizerModeLock) { + assertThat(mDtm.mDaltonizerMode).isEqualTo(0); + assertThat(mDtm.mDaltonizerLevel).isEqualTo(0); + } + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index cd1e9e85afb5..714b423fae70 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java @@ -131,7 +131,8 @@ public class DisplayModeDirectorTest { /* defaultRefreshRate= */ 0, /* defaultPeakRefreshRate= */ 0, /* defaultRefreshRateInHbmHdr= */ 0, - /* defaultRefreshRateInHbmSunlight= */ 0); + /* defaultRefreshRateInHbmSunlight= */ 0, + /* lowPowerSupportedModes =*/ List.of()); public static Collection<Object[]> getAppRequestedSizeTestCases() { var appRequestedSizeTestCases = Arrays.asList(new Object[][] { @@ -157,7 +158,7 @@ public class DisplayModeDirectorTest { APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), @@ -169,7 +170,7 @@ public class DisplayModeDirectorTest { APP_MODE_65.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(), LIMIT_MODE_70.getPhysicalHeight()))}, {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(), @@ -181,7 +182,7 @@ public class DisplayModeDirectorTest { APP_MODE_65.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSizeAndPhysicalRefreshRatesRange( 0, 0, LIMIT_MODE_70.getPhysicalWidth(), @@ -197,7 +198,7 @@ public class DisplayModeDirectorTest { APP_MODE_65.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_65.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSizeAndPhysicalRefreshRatesRange( 0, 0, LIMIT_MODE_70.getPhysicalWidth(), @@ -213,7 +214,7 @@ public class DisplayModeDirectorTest { APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSizeAndPhysicalRefreshRatesRange( 0, 0, LIMIT_MODE_70.getPhysicalWidth(), @@ -229,7 +230,7 @@ public class DisplayModeDirectorTest { APP_MODE_HIGH_90.getPhysicalHeight()), Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forSizeAndPhysicalRefreshRatesRange( 0, 0, LIMIT_MODE_70.getPhysicalWidth(), @@ -245,7 +246,7 @@ public class DisplayModeDirectorTest { Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(APP_MODE_65.getPhysicalWidth(), APP_MODE_65.getPhysicalHeight()), - Vote.PRIORITY_LOW_POWER_MODE, + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forPhysicalRefreshRates( 0, 64.99f))}}); @@ -598,7 +599,7 @@ public class DisplayModeDirectorTest { < Vote.PRIORITY_APP_REQUEST_SIZE); assertTrue(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH - > Vote.PRIORITY_LOW_POWER_MODE); + > Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE); Display.Mode[] modes = new Display.Mode[4]; modes[0] = new Display.Mode( @@ -676,9 +677,9 @@ public class DisplayModeDirectorTest { @Test public void testLPMHasHigherPriorityThanUser() { - assertTrue(Vote.PRIORITY_LOW_POWER_MODE + assertTrue(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE > Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE); - assertTrue(Vote.PRIORITY_LOW_POWER_MODE + assertTrue(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE > Vote.PRIORITY_APP_REQUEST_SIZE); Display.Mode[] modes = new Display.Mode[4]; @@ -700,7 +701,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), appRequestedMode.getPhysicalHeight())); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(2); @@ -715,7 +716,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), appRequestedMode.getPhysicalHeight())); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(4); @@ -730,7 +731,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), appRequestedMode.getPhysicalHeight())); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(60, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(60, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(2); @@ -745,7 +746,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_APP_REQUEST_SIZE, Vote.forSize(appRequestedMode.getPhysicalWidth(), appRequestedMode.getPhysicalHeight())); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.baseModeId).isEqualTo(4); @@ -906,7 +907,7 @@ public class DisplayModeDirectorTest { Vote.forBaseModeRefreshRate(appRequestedMode.getRefreshRate())); votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, Vote.forRenderFrameRates(60, 60)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); assertThat(director.shouldAlwaysRespectAppRequestedMode()).isFalse(); @@ -946,7 +947,7 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(30, 90)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); assertThat(director.getModeSwitchingType()) @@ -987,7 +988,7 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(30, 90)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); assertThat(director.getModeSwitchingType()) @@ -1029,7 +1030,7 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(30, 90)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); assertThat(director.getModeSwitchingType()) @@ -1900,7 +1901,7 @@ public class DisplayModeDirectorTest { director.start(createMockSensorManager()); SparseArray<Vote> votes = new SparseArray<>(); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 50f)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 50f)); SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID_2, votes); @@ -2298,7 +2299,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(90, Float.POSITIVE_INFINITY)); votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(50); @@ -2311,7 +2312,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY)); votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 90)); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(80); assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(80); @@ -2323,7 +2324,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_USER_SETTING_MIN_RENDER_FRAME_RATE, Vote.forRenderFrameRates(80, Float.POSITIVE_INFINITY)); votes.put(Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH, Vote.forDisableRefreshRateSwitching()); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 90)); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(90); assertThat(desiredSpecs.primary.physical.max).isWithin(FLOAT_TOLERANCE).of(90); @@ -2343,7 +2344,7 @@ public class DisplayModeDirectorTest { votesByDisplay.put(DISPLAY_ID, votes); votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(70)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); @@ -2360,7 +2361,7 @@ public class DisplayModeDirectorTest { votes.clear(); votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(55)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); @@ -2374,7 +2375,7 @@ public class DisplayModeDirectorTest { Vote.forRenderFrameRates(0, 52)); votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(55)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); @@ -2392,7 +2393,7 @@ public class DisplayModeDirectorTest { Vote.forRenderFrameRates(0, 58)); votes.put(Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE, Vote.forBaseModeRefreshRate(55)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); director.injectVotesByDisplay(votesByDisplay); desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.primary.physical.min).isWithin(FLOAT_TOLERANCE).of(0); @@ -2521,7 +2522,7 @@ public class DisplayModeDirectorTest { votes.put(Vote.PRIORITY_UDFPS, Vote.forPhysicalRefreshRates(120, 120)); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(90, 90)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(90, 90)); director.injectVotesByDisplay(votesByDisplay); DesiredDisplayModeSpecs desiredSpecs = director.getDesiredDisplayModeSpecs(DISPLAY_ID); assertThat(desiredSpecs.appRequest.physical.min).isWithin(FLOAT_TOLERANCE).of(120); @@ -2542,7 +2543,7 @@ public class DisplayModeDirectorTest { SparseArray<SparseArray<Vote>> votesByDisplay = new SparseArray<>(); votesByDisplay.put(DISPLAY_ID, votes); - votes.put(Vote.PRIORITY_LOW_POWER_MODE, Vote.forRenderFrameRates(0, 60)); + votes.put(Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE, Vote.forRenderFrameRates(0, 60)); votes.put(Vote.PRIORITY_USER_SETTING_PEAK_RENDER_FRAME_RATE, Vote.forRenderFrameRates(0, 30)); director.injectVotesByDisplay(votesByDisplay); @@ -3168,7 +3169,8 @@ public class DisplayModeDirectorTest { /* defaultRefreshRate= */ 60, /* defaultPeakRefreshRate= */ 65, /* defaultRefreshRateInHbmHdr= */ 65, - /* defaultRefreshRateInHbmSunlight= */ 75); + /* defaultRefreshRateInHbmSunlight= */ 75, + /* lowPowerSupportedModes= */ List.of()); when(displayDeviceConfig.getRefreshRateData()).thenReturn(refreshRateData); when(displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()).thenReturn(50); when(displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()).thenReturn(55); @@ -3390,9 +3392,10 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass(DisplayListener.class); - verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + verify(mInjector, atLeastOnce()).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class)); - DisplayListener displayListener = displayListenerCaptor.getValue(); + // DisplayObserver should register first + DisplayListener displayListener = displayListenerCaptor.getAllValues().get(0); float refreshRate = 60; mInjector.mDisplayInfo.layoutLimitedRefreshRate = @@ -3417,9 +3420,10 @@ public class DisplayModeDirectorTest { ArgumentCaptor<DisplayListener> displayListenerCaptor = ArgumentCaptor.forClass(DisplayListener.class); - verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(), + verify(mInjector, atLeastOnce()).registerDisplayListener(displayListenerCaptor.capture(), any(Handler.class)); - DisplayListener displayListener = displayListenerCaptor.getValue(); + // DisplayObserver should register first + DisplayListener displayListener = displayListenerCaptor.getAllValues().get(0); mInjector.mDisplayInfo.layoutLimitedRefreshRate = new RefreshRateRange(10, 10); mInjector.mDisplayInfoValid = false; diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java index 2d317af3d85d..ee79d196cfd9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java @@ -407,7 +407,8 @@ public class DisplayObserverTest { assertThat(mObserver).isNull(); mObserver = invocation.getArgument(0); return null; - }).when(mInjector).registerDisplayListener(any(), any()); + }).when(mInjector).registerDisplayListener( + any(DisplayModeDirector.DisplayObserver.class), any()); doAnswer(c -> { DisplayInfo info = c.getArgument(1); diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt index 4d910cefdb79..e431c8c3555c 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SettingsObserverTest.kt @@ -27,8 +27,11 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.filters.SmallTest import com.android.internal.util.test.FakeSettingsProvider import com.android.server.display.DisplayDeviceConfig +import com.android.server.display.config.RefreshRateData +import com.android.server.display.config.SupportedModeData import com.android.server.display.feature.DisplayManagerFlags import com.android.server.display.mode.DisplayModeDirector.DisplayDeviceConfigProvider +import com.android.server.display.mode.SupportedRefreshRatesVote.RefreshRates import com.android.server.testutils.TestHandler import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage @@ -69,6 +72,13 @@ private val RANGES_MIN90_90TO120 = RefreshRateRanges(RANGE_90_INF, RANGE_90_120) private val RANGES_MIN60_60TO90 = RefreshRateRanges(RANGE_60_INF, RANGE_60_90) private val RANGES_MIN90_90TO90 = RefreshRateRanges(RANGE_90_INF, RANGE_90_90) +private val LOW_POWER_GLOBAL_VOTE = Vote.forRenderFrameRates(0f, 60f) +private val LOW_POWER_REFRESH_RATE_DATA = createRefreshRateData( + lowPowerSupportedModes = listOf(SupportedModeData(60f, 60f), SupportedModeData(60f, 240f))) +private val LOW_POWER_EMPTY_REFRESH_RATE_DATA = createRefreshRateData() +private val EXPECTED_SUPPORTED_MODES_VOTE = SupportedRefreshRatesVote( + listOf(RefreshRates(60f, 60f), RefreshRates(60f, 240f))) + @SmallTest @RunWith(TestParameterInjector::class) class SettingsObserverTest { @@ -103,7 +113,7 @@ class SettingsObserverTest { val displayModeDirector = DisplayModeDirector( spyContext, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider) val ddcByDisplay = SparseArray<DisplayDeviceConfig>() - whenever(mockDeviceConfig.isVrrSupportEnabled).thenReturn(testCase.vrrSupported) + whenever(mockDeviceConfig.refreshRateData).thenReturn(testCase.refreshRateData) ddcByDisplay.put(Display.DEFAULT_DISPLAY, mockDeviceConfig) displayModeDirector.injectDisplayDeviceConfigByDisplay(ddcByDisplay) val settingsObserver = displayModeDirector.SettingsObserver( @@ -113,27 +123,30 @@ class SettingsObserverTest { false, Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE), 1) assertThat(displayModeDirector.getVote(VotesStorage.GLOBAL_ID, - Vote.PRIORITY_LOW_POWER_MODE)).isEqualTo(testCase.expectedVote) + Vote.PRIORITY_LOW_POWER_MODE_RENDER_RATE)).isEqualTo(testCase.globalVote) + assertThat(displayModeDirector.getVote(Display.DEFAULT_DISPLAY, + Vote.PRIORITY_LOW_POWER_MODE_MODES)).isEqualTo(testCase.displayVote) } enum class LowPowerTestCase( - val vrrSupported: Boolean, + val refreshRateData: RefreshRateData, val vsyncLowPowerVoteEnabled: Boolean, val lowPowerModeEnabled: Boolean, - internal val expectedVote: Vote? + internal val globalVote: Vote?, + internal val displayVote: Vote? ) { - ALL_ENABLED(true, true, true, - SupportedRefreshRatesVote(listOf( - SupportedRefreshRatesVote.RefreshRates(60f, 240f), - SupportedRefreshRatesVote.RefreshRates(60f, 60f) - ))), - LOW_POWER_OFF(true, true, false, null), - DVRR_NOT_SUPPORTED_LOW_POWER_ON(false, true, true, - RefreshRateVote.RenderVote(0f, 60f)), - DVRR_NOT_SUPPORTED_LOW_POWER_OFF(false, true, false, null), - VSYNC_VOTE_DISABLED_SUPPORTED_LOW_POWER_ON(true, false, true, - RefreshRateVote.RenderVote(0f, 60f)), - VSYNC_VOTE_DISABLED_LOW_POWER_OFF(true, false, false, null), + ALL_ENABLED(LOW_POWER_REFRESH_RATE_DATA, true, true, + LOW_POWER_GLOBAL_VOTE, EXPECTED_SUPPORTED_MODES_VOTE), + LOW_POWER_OFF(LOW_POWER_REFRESH_RATE_DATA, true, false, + null, null), + EMPTY_REFRESH_LOW_POWER_ON(LOW_POWER_EMPTY_REFRESH_RATE_DATA, true, true, + LOW_POWER_GLOBAL_VOTE, null), + EMPTY_REFRESH__LOW_POWER_OFF(LOW_POWER_EMPTY_REFRESH_RATE_DATA, true, false, + null, null), + VSYNC_VOTE_DISABLED_SUPPORTED_LOW_POWER_ON(LOW_POWER_REFRESH_RATE_DATA, false, true, + LOW_POWER_GLOBAL_VOTE, null), + VSYNC_VOTE_DISABLED_LOW_POWER_OFF(LOW_POWER_REFRESH_RATE_DATA, false, false, + null, null), } @Test diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt index 6b90bde188c5..1206e30b9e88 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/TestUtils.kt @@ -16,6 +16,9 @@ package com.android.server.display.mode +import com.android.server.display.config.RefreshRateData +import com.android.server.display.config.SupportedModeData + internal fun createVotesSummary( isDisplayResolutionRangeVotingEnabled: Boolean = true, supportedModesVoteEnabled: Boolean = true, @@ -24,4 +27,16 @@ internal fun createVotesSummary( ): VoteSummary { return VoteSummary(isDisplayResolutionRangeVotingEnabled, supportedModesVoteEnabled, loggingEnabled, supportsFrameRateOverride) -}
\ No newline at end of file +} + +fun createRefreshRateData( + defaultRefreshRate: Int = 60, + defaultPeakRefreshRate: Int = 60, + defaultRefreshRateInHbmHdr: Int = 60, + defaultRefreshRateInHbmSunlight: Int = 60, + lowPowerSupportedModes: List<SupportedModeData> = emptyList() +): RefreshRateData { + return RefreshRateData(defaultRefreshRate, defaultPeakRefreshRate, + defaultRefreshRateInHbmHdr, defaultRefreshRateInHbmSunlight, + lowPowerSupportedModes) +} diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java index 88ab871529ee..874e99173c63 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamControllerTest.java @@ -273,28 +273,36 @@ public class DreamControllerTest { } @Test - public void setDreamHasFocus_true_dreamHasFocus() { + public void setDreamIsObscured_true_dreamIsNotFrontmost() { mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); - mDreamController.setDreamHasFocus(true); - assertTrue(mDreamController.dreamHasFocus()); + mDreamController.setDreamIsObscured(true); + assertFalse(mDreamController.dreamIsFrontmost()); } @Test - public void setDreamHasFocus_false_dreamDoesNotHaveFocus() { + public void setDreamIsObscured_false_dreamIsFrontmost() { mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); - mDreamController.setDreamHasFocus(false); - assertFalse(mDreamController.dreamHasFocus()); + mDreamController.setDreamIsObscured(false); + assertTrue(mDreamController.dreamIsFrontmost()); } @Test - public void setDreamHasFocus_notDreaming_dreamDoesNotHaveFocus() { - mDreamController.setDreamHasFocus(true); - // Dream still doesn't have focus because it was never started. - assertFalse(mDreamController.dreamHasFocus()); + public void setDreamIsObscured_notDreaming_dreamIsNotFrontmost() { + mDreamController.setDreamIsObscured(true); + // Dream still isn't frontmost because it was never started. + assertFalse(mDreamController.dreamIsFrontmost()); + } + + @Test + public void startDream_dreamIsFrontmost() { + mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, + 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); + + assertTrue(mDreamController.dreamIsFrontmost()); } private ServiceConnection captureServiceConnection() { diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java index 1322545c8d7e..b98af6bc7dd0 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java @@ -37,6 +37,7 @@ import android.service.dreams.DreamService; import android.service.dreams.Flags; import android.service.dreams.IDreamOverlayCallback; import android.testing.TestableLooper; +import android.view.KeyEvent; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; @@ -181,4 +182,15 @@ public class DreamServiceTest { environment.advance(TestDreamEnvironment.DREAM_STATE_WOKEN); verify(environment.getDreamOverlayClient()).onWakeRequested(); } + + @Test + @EnableFlags(Flags.FLAG_DREAM_HANDLES_CONFIRM_KEYS) + public void testPartialKeyHandling() throws Exception { + TestDreamEnvironment environment = new TestDreamEnvironment.Builder(mTestableLooper) + .build(); + environment.advance(TestDreamEnvironment.DREAM_STATE_STARTED); + + // Ensure service does not crash from only receiving up event. + environment.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE)); + } } diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java index ef85ba56769e..3d03bf218557 100644 --- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java +++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java @@ -46,6 +46,7 @@ import android.service.dreams.IDreamOverlayCallback; import android.service.dreams.IDreamOverlayClient; import android.service.dreams.IDreamService; import android.testing.TestableLooper; +import android.view.KeyEvent; import android.view.View; import android.view.Window; import android.view.WindowInsetsController; @@ -390,6 +391,13 @@ public class TestDreamEnvironment { } } + /** + * Sends a key event to the dream. + */ + public void dispatchKeyEvent(KeyEvent event) { + mService.dispatchKeyEvent(event); + } + private void wakeDream() throws RemoteException { mService.wakeUp(); } 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/SensitiveContentProtectionManagerServiceContentTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java index 7aafa8e92690..5ddd8a50135b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceContentTest.java @@ -26,7 +26,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; import android.content.pm.PackageManagerInternal; import android.media.projection.MediaProjectionInfo; @@ -108,7 +107,7 @@ public class SensitiveContentProtectionManagerServiceContentTest { mMediaPorjectionCallback.onStart(exemptedRecorderPackage); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -135,7 +134,7 @@ public class SensitiveContentProtectionManagerServiceContentTest { // when screen sharing is not active, no app window should be blocked. mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -158,8 +157,7 @@ public class SensitiveContentProtectionManagerServiceContentTest { mMediaPorjectionCallback.onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); - verify(mWindowManager, never()) - .addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -168,7 +166,7 @@ public class SensitiveContentProtectionManagerServiceContentTest { mMediaProjectionCallbackCaptor.getValue().onStart(mMediaProjectionInfo); mSensitiveContentProtectionManagerService.setSensitiveContentProtection( mPackageInfo.getWindowToken(), mPackageInfo.getPkg(), mPackageInfo.getUid(), true); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } private void mockDisabledViaDeveloperOption() { diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java index a20d935c50aa..8b653378664e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceNotificationTest.java @@ -30,7 +30,6 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.pm.PackageManagerInternal; @@ -102,6 +101,8 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { @Captor ArgumentCaptor<MediaProjectionManager.Callback> mMediaProjectionCallbackCaptor; + @Captor + private ArgumentCaptor<ArraySet<PackageInfo>> mPackageInfoCaptor; @Mock private MediaProjectionManager mProjectionManager; @@ -309,7 +310,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -469,7 +470,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -480,7 +481,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -495,7 +496,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -519,7 +520,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -530,7 +531,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -541,7 +542,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -557,7 +558,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -574,7 +575,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -586,7 +587,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mMediaProjectionCallbackCaptor.getValue().onStart(createMediaProjectionInfo()); mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected(); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -598,7 +599,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verifyZeroInteractions(mWindowManager); + verifyNoBlockOrClearInteractionWithWindowManager(); } @Test @@ -614,7 +615,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verifyZeroInteractions(mWindowManager); + verifyNoBlockOrClearInteractionWithWindowManager(); } @Test @@ -640,7 +641,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -652,7 +653,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -666,7 +667,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(null); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -684,7 +685,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -702,7 +703,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -715,7 +716,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationRankingUpdate(mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -727,7 +728,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationPosted(mNotification1, mRankingMap); - verifyZeroInteractions(mWindowManager); + verifyNoBlockOrClearInteractionWithWindowManager(); } @Test @@ -743,7 +744,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationPosted(mNotification1, mRankingMap); - verifyZeroInteractions(mWindowManager); + verifyNoBlockOrClearInteractionWithWindowManager(); } @Test @@ -773,7 +774,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationPosted(mNotification2, mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -787,7 +788,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationPosted(null, mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -801,7 +802,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationPosted(mNotification1, null); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -816,7 +817,7 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationPosted(mNotification1, mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } @Test @@ -829,7 +830,14 @@ public class SensitiveContentProtectionManagerServiceNotificationTest { mSensitiveContentProtectionManagerService.mNotificationListener .onNotificationPosted(mNotification1, mRankingMap); - verifyZeroInteractions(mWindowManager); + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); + } + + private void verifyNoBlockOrClearInteractionWithWindowManager() { + verify(mWindowManager, never()).addBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); + verify(mWindowManager, never()).clearBlockedApps(); + verify(mWindowManager, never()) + .removeBlockScreenCaptureForApps(mPackageInfoCaptor.capture()); } private void mockDisabledViaDevelopOption() { diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java index c359412b6ccd..cb15d6f84403 100644 --- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java @@ -3094,13 +3094,14 @@ public final class AlarmManagerServiceTest { ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class), any(), any(Handler.class), isNull(), bundleCaptor.capture()); + Bundle bundle = bundleCaptor.getValue(); if (idleOptions != null) { - assertEquals(idleOptions, bundleCaptor.getValue()); + assertEquals(idleOptions, bundle); } else { - assertFalse("BAL flag needs to be false in alarm manager", - bundleCaptor.getValue().getBoolean( - ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, - true)); + ActivityOptions options = ActivityOptions.fromBundle(bundle); + assertEquals("BAL should not be allowed in alarm manager", + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED, + options.getPendingIntentBackgroundActivityStartMode()); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java index c1f4feee9c57..e88e28b37551 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java @@ -142,6 +142,7 @@ public class ApplicationStartInfoTest { final String app1PackageName = "com.android.test.stub1"; final long appStartTimestampIntentStarted = 1000000; final long appStartTimestampActivityLaunchFinished = 2000000; + final long appStartTimestampFirstFrameDrawn = 2500000; final long appStartTimestampReportFullyDrawn = 3000000; final long appStartTimestampService = 4000000; final long appStartTimestampBroadcast = 5000000; @@ -272,6 +273,8 @@ public class ApplicationStartInfoTest { mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT, appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD); + mAppStartInfoTracker.addTimestampToStart(app1PackageName, app1Uid, + appStartTimestampFirstFrameDrawn, ApplicationStartInfo.START_TIMESTAMP_FIRST_FRAME); list.clear(); mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list); verifyInProgressRecordsSize(1); 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/OWNERS b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS index 72c0a9e6e90c..2cbc226da780 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/OWNERS +++ b/services/tests/mockingservicestests/src/com/android/server/am/OWNERS @@ -1 +1,3 @@ include /services/core/java/com/android/server/am/OWNERS + +per-file ApplicationStartInfoTest.java = yforta@google.com, carmenjackson@google.com, jji@google.com 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..c77ab0f303d9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/SettingsToPropertiesMapperTest.java @@ -24,7 +24,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import android.content.ContentResolver; import android.os.SystemProperties; import android.provider.Settings; -import android.provider.DeviceConfig.Properties; import android.text.TextUtils; import com.android.dx.mockito.inline.extended.ExtendedMockito; @@ -43,7 +42,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; /** * Test SettingsToPropertiesMapper. @@ -63,7 +61,6 @@ public class SettingsToPropertiesMapperTest { private HashMap<String, String> mSystemSettingsMap; private HashMap<String, String> mGlobalSettingsMap; - private HashMap<String, String> mConfigSettingsMap; @Before public void setUp() throws Exception { @@ -74,11 +71,9 @@ public class SettingsToPropertiesMapperTest { .spyStatic(SystemProperties.class) .spyStatic(Settings.Global.class) .spyStatic(SettingsToPropertiesMapper.class) - .spyStatic(Settings.Config.class) .startMocking(); mSystemSettingsMap = new HashMap<>(); mGlobalSettingsMap = new HashMap<>(); - mConfigSettingsMap = new HashMap<>(); // Mock SystemProperties setter and various getters doAnswer((Answer<Void>) invocationOnMock -> { @@ -106,21 +101,6 @@ public class SettingsToPropertiesMapperTest { } ).when(() -> Settings.Global.getString(any(), anyString())); - // Mock Settings.Config getstrings method - doAnswer((Answer<Map<String, String>>) invocationOnMock -> { - String namespace = invocationOnMock.getArgument(0); - List<String> flags = invocationOnMock.getArgument(1); - HashMap<String, String> values = new HashMap<>(); - for (String flag : flags) { - String value = mConfigSettingsMap.get(namespace + "/" + flag); - if (value != null) { - values.put(flag, value); - } - } - return values; - } - ).when(() -> Settings.Config.getStrings(anyString(), any())); - mTestMapper = new SettingsToPropertiesMapper( mMockContentResolver, TEST_MAPPING, new String[] {}, new String[] {}); } @@ -259,43 +239,4 @@ public class SettingsToPropertiesMapperTest { Assert.assertTrue(categories.contains("category2")); Assert.assertTrue(categories.contains("category3")); } - - @Test - public void testGetStagedFlagsWithValueChange() { - // mock up what is in the setting already - mConfigSettingsMap.put("namespace_1/flag_1", "true"); - mConfigSettingsMap.put("namespace_1/flag_2", "true"); - - // mock up input - String namespace = "staged"; - Map<String, String> keyValueMap = new HashMap<>(); - // case 1: existing prop, stage the same value - 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 - 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 = - SettingsToPropertiesMapper.getStagedFlagsWithValueChange(props); - - HashMap<String, String> namespace_1_to_stage = toStageProps.get("namespace_1"); - HashMap<String, String> namespace_2_to_stage = toStageProps.get("namespace_2"); - Assert.assertTrue(namespace_1_to_stage != null); - Assert.assertTrue(namespace_2_to_stage != null); - - 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/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index 11f20e35b4b1..d15c24bd68b9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -31,6 +31,7 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static com.android.server.job.JobSchedulerService.sUptimeMillisClock; import static com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS; import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK; +import static com.android.server.job.Flags.FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -74,6 +75,9 @@ import android.os.Looper; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.platform.test.flag.junit.SetFlagsRule; import com.android.server.AppStateTracker; @@ -85,6 +89,8 @@ import com.android.server.SystemServiceManager; import com.android.server.job.controllers.ConnectivityController; import com.android.server.job.controllers.JobStatus; import com.android.server.job.controllers.QuotaController; +import com.android.server.job.restrictions.JobRestriction; +import com.android.server.job.restrictions.ThermalStatusRestriction; import com.android.server.pm.UserManagerInternal; import com.android.server.usage.AppStandbyInternal; @@ -121,6 +127,9 @@ public class JobSchedulerServiceTest { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private ChargingPolicyChangeListener mChargingPolicyChangeListener; private class TestJobSchedulerService extends JobSchedulerService { @@ -2385,6 +2394,108 @@ public class JobSchedulerServiceTest { assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b)); } + /** + * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with single {@link + * JobRestriction} registered. + */ + @Test + public void testCheckIfRestrictedSingleRestriction() { + int bias = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE; + JobStatus fgsJob = + createJobStatus( + "testCheckIfRestrictedSingleRestriction", createJobInfo(1).setBias(bias)); + ThermalStatusRestriction mockThermalStatusRestriction = + mock(ThermalStatusRestriction.class); + mService.mJobRestrictions.clear(); + mService.mJobRestrictions.add(mockThermalStatusRestriction); + when(mockThermalStatusRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true); + + synchronized (mService.mLock) { + assertEquals(mService.checkIfRestricted(fgsJob), mockThermalStatusRestriction); + } + + when(mockThermalStatusRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false); + synchronized (mService.mLock) { + assertNull(mService.checkIfRestricted(fgsJob)); + } + } + + /** + * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with multiple {@link + * JobRestriction} registered. + */ + @Test + public void testCheckIfRestrictedMultipleRestrictions() { + int bias = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE; + JobStatus fgsJob = + createJobStatus( + "testGetMinJobExecutionGuaranteeMs", createJobInfo(1).setBias(bias)); + JobRestriction mock1JobRestriction = mock(JobRestriction.class); + JobRestriction mock2JobRestriction = mock(JobRestriction.class); + mService.mJobRestrictions.clear(); + mService.mJobRestrictions.add(mock1JobRestriction); + mService.mJobRestrictions.add(mock2JobRestriction); + + // Jobs will be restricted if any one of the registered {@link JobRestriction} + // reports true. + when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true); + when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false); + synchronized (mService.mLock) { + assertEquals(mService.checkIfRestricted(fgsJob), mock1JobRestriction); + } + + when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false); + when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true); + synchronized (mService.mLock) { + assertEquals(mService.checkIfRestricted(fgsJob), mock2JobRestriction); + } + + when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false); + when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false); + synchronized (mService.mLock) { + assertNull(mService.checkIfRestricted(fgsJob)); + } + + when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true); + when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true); + synchronized (mService.mLock) { + assertNotEquals(mService.checkIfRestricted(fgsJob), mock1JobRestriction); + } + } + + /** + * Jobs with foreground service and top app biases must not be restricted when the flag is + * disabled. + */ + @Test + @RequiresFlagsDisabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS) + public void testCheckIfRestricted_highJobBias_flagThermalRestrictionsToFgsJobsDisabled() { + JobStatus fgsJob = + createJobStatus( + "testCheckIfRestrictedJobBiasFgs", + createJobInfo(1).setBias(JobInfo.BIAS_FOREGROUND_SERVICE)); + JobStatus topAppJob = + createJobStatus( + "testCheckIfRestrictedJobBiasTopApp", + createJobInfo(2).setBias(JobInfo.BIAS_TOP_APP)); + + synchronized (mService.mLock) { + assertNull(mService.checkIfRestricted(fgsJob)); + assertNull(mService.checkIfRestricted(topAppJob)); + } + } + + /** Jobs with top app biases must not be restricted. */ + @Test + public void testCheckIfRestricted_highJobBias() { + JobStatus topAppJob = createJobStatus( + "testCheckIfRestrictedJobBiasTopApp", + createJobInfo(1).setBias(JobInfo.BIAS_TOP_APP)); + synchronized (mService.mLock) { + assertNull(mService.checkIfRestricted(topAppJob)); + } + } + private void setBatteryLevel(int level) { doReturn(level).when(mBatteryManagerInternal).getBatteryLevel(); mService.mBatteryStateTracker diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java index 754f409b3966..c2c67e615228 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java @@ -28,6 +28,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; +import static com.android.server.job.Flags.FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -43,7 +44,12 @@ import android.app.job.JobInfo; import android.content.ComponentName; import android.content.Context; import android.os.PowerManager; +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.DeviceConfig; +import android.util.DebugUtils; import androidx.test.runner.AndroidJUnit4; @@ -53,6 +59,7 @@ import com.android.server.job.controllers.JobStatus; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -76,6 +83,157 @@ public class ThermalStatusRestrictionTest { @Mock private JobSchedulerService mJobSchedulerService; + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + class JobStatusContainer { + public final JobStatus jobMinPriority; + public final JobStatus jobLowPriority; + public final JobStatus jobLowPriorityRunning; + public final JobStatus jobLowPriorityRunningLong; + public final JobStatus jobDefaultPriority; + public final JobStatus jobHighPriority; + public final JobStatus jobHighPriorityRunning; + public final JobStatus jobHighPriorityRunningLong; + public final JobStatus ejDowngraded; + public final JobStatus ej; + public final JobStatus ejRetried; + public final JobStatus ejRunning; + public final JobStatus ejRunningLong; + public final JobStatus ui; + public final JobStatus uiRetried; + public final JobStatus uiRunning; + public final JobStatus uiRunningLong; + public final JobStatus importantWhileForeground; + public final JobStatus importantWhileForegroundRunning; + public final JobStatus importantWhileForegroundRunningLong; + public final int[] allJobBiases = { + JobInfo.BIAS_ADJ_ALWAYS_RUNNING, + JobInfo.BIAS_ADJ_OFTEN_RUNNING, + JobInfo.BIAS_DEFAULT, + JobInfo.BIAS_SYNC_EXPEDITED, + JobInfo.BIAS_SYNC_INITIALIZATION, + JobInfo.BIAS_BOUND_FOREGROUND_SERVICE, + JobInfo.BIAS_FOREGROUND_SERVICE, + JobInfo.BIAS_TOP_APP + }; + public final int[] biasesBelowFgs = { + JobInfo.BIAS_ADJ_ALWAYS_RUNNING, + JobInfo.BIAS_ADJ_OFTEN_RUNNING, + JobInfo.BIAS_DEFAULT, + JobInfo.BIAS_SYNC_EXPEDITED, + JobInfo.BIAS_SYNC_INITIALIZATION, + JobInfo.BIAS_BOUND_FOREGROUND_SERVICE + }; + public final int[] thermalStatuses = { + THERMAL_STATUS_NONE, + THERMAL_STATUS_LIGHT, + THERMAL_STATUS_MODERATE, + THERMAL_STATUS_SEVERE, + THERMAL_STATUS_CRITICAL, + THERMAL_STATUS_EMERGENCY, + THERMAL_STATUS_SHUTDOWN + }; + + JobStatusContainer(String jobName, JobSchedulerService mJobSchedulerService) { + jobMinPriority = + createJobStatus( + jobName, createJobBuilder(1).setPriority(JobInfo.PRIORITY_MIN).build()); + jobLowPriority = + createJobStatus( + jobName, createJobBuilder(2).setPriority(JobInfo.PRIORITY_LOW).build()); + jobLowPriorityRunning = + createJobStatus( + jobName, createJobBuilder(3).setPriority(JobInfo.PRIORITY_LOW).build()); + jobLowPriorityRunningLong = + createJobStatus( + jobName, createJobBuilder(9).setPriority(JobInfo.PRIORITY_LOW).build()); + jobDefaultPriority = + createJobStatus( + jobName, + createJobBuilder(4).setPriority(JobInfo.PRIORITY_DEFAULT).build()); + jobHighPriority = + createJobStatus( + jobName, + createJobBuilder(5).setPriority(JobInfo.PRIORITY_HIGH).build()); + jobHighPriorityRunning = + createJobStatus( + jobName, + createJobBuilder(6).setPriority(JobInfo.PRIORITY_HIGH).build()); + jobHighPriorityRunningLong = + createJobStatus( + jobName, + createJobBuilder(10).setPriority(JobInfo.PRIORITY_HIGH).build()); + ejDowngraded = createJobStatus(jobName, createJobBuilder(7).setExpedited(true).build()); + ej = spy(createJobStatus(jobName, createJobBuilder(8).setExpedited(true).build())); + ejRetried = + spy(createJobStatus(jobName, createJobBuilder(11).setExpedited(true).build())); + ejRunning = + spy(createJobStatus(jobName, createJobBuilder(12).setExpedited(true).build())); + ejRunningLong = + spy(createJobStatus(jobName, createJobBuilder(13).setExpedited(true).build())); + ui = spy(createJobStatus(jobName, createJobBuilder(14).build())); + uiRetried = spy(createJobStatus(jobName, createJobBuilder(15).build())); + uiRunning = spy(createJobStatus(jobName, createJobBuilder(16).build())); + uiRunningLong = spy(createJobStatus(jobName, createJobBuilder(17).build())); + importantWhileForeground = spy(createJobStatus(jobName, createJobBuilder(18) + .setImportantWhileForeground(true) + .build())); + importantWhileForegroundRunning = spy(createJobStatus(jobName, createJobBuilder(20) + .setImportantWhileForeground(true) + .build())); + importantWhileForegroundRunningLong = spy(createJobStatus(jobName, createJobBuilder(19) + .setImportantWhileForeground(true) + .build())); + + when(ej.shouldTreatAsExpeditedJob()).thenReturn(true); + when(ejRetried.shouldTreatAsExpeditedJob()).thenReturn(true); + when(ejRunning.shouldTreatAsExpeditedJob()).thenReturn(true); + when(ejRunningLong.shouldTreatAsExpeditedJob()).thenReturn(true); + when(ui.shouldTreatAsUserInitiatedJob()).thenReturn(true); + when(uiRetried.shouldTreatAsUserInitiatedJob()).thenReturn(true); + when(uiRunning.shouldTreatAsUserInitiatedJob()).thenReturn(true); + when(uiRunningLong.shouldTreatAsUserInitiatedJob()).thenReturn(true); + when(ejRetried.getNumPreviousAttempts()).thenReturn(1); + when(uiRetried.getNumPreviousAttempts()).thenReturn(2); + when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)) + .thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning)) + .thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunningLong)) + .thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunningLong)) + .thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunning)).thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunningLong)).thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunning)).thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunningLong)).thenReturn(true); + when(mJobSchedulerService.isJobInOvertimeLocked(jobLowPriorityRunningLong)) + .thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(importantWhileForegroundRunning)) + .thenReturn(true); + when(mJobSchedulerService.isJobInOvertimeLocked(jobHighPriorityRunningLong)) + .thenReturn(true); + when(mJobSchedulerService.isJobInOvertimeLocked(ejRunningLong)).thenReturn(true); + when(mJobSchedulerService.isJobInOvertimeLocked(uiRunningLong)).thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(importantWhileForegroundRunningLong)) + .thenReturn(true); + when(mJobSchedulerService.isJobInOvertimeLocked(importantWhileForegroundRunningLong)) + .thenReturn(true); + } + } + + private boolean isJobRestricted(JobStatus status, int bias) { + return mThermalStatusRestriction.isJobRestricted(status, bias); + } + + private static String debugTag(int bias, @PowerManager.ThermalStatus int status) { + return "Bias = " + + JobInfo.getBiasString(bias) + + " Thermal Status = " + + DebugUtils.valueToString(PowerManager.class, "THERMAL_STATUS_", status); + } + @Before public void setUp() { mMockingSession = mockitoSession() @@ -156,169 +314,302 @@ public class ThermalStatusRestrictionTest { assertEquals(THERMAL_STATUS_EMERGENCY, mThermalStatusRestriction.getThermalStatus()); } + /** + * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Thermal is in default state + */ @Test - public void testIsJobRestricted() { + public void testIsJobRestrictedDefaultStates() { mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_NONE); + JobStatusContainer jc = new JobStatusContainer("testIsJobRestricted", mJobSchedulerService); + + for (int jobBias : jc.allJobBiases) { + assertFalse(isJobRestricted(jc.jobMinPriority, jobBias)); + assertFalse(isJobRestricted(jc.jobLowPriority, jobBias)); + assertFalse(isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertFalse(isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + assertFalse(isJobRestricted(jc.jobDefaultPriority, jobBias)); + assertFalse(isJobRestricted(jc.jobHighPriority, jobBias)); + assertFalse(isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertFalse(isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + assertFalse(isJobRestricted(jc.importantWhileForeground, jobBias)); + assertFalse(isJobRestricted(jc.importantWhileForegroundRunning, jobBias)); + assertFalse(isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias)); + assertFalse(isJobRestricted(jc.ej, jobBias)); + assertFalse(isJobRestricted(jc.ejDowngraded, jobBias)); + assertFalse(isJobRestricted(jc.ejRetried, jobBias)); + assertFalse(isJobRestricted(jc.ejRunning, jobBias)); + assertFalse(isJobRestricted(jc.ejRunningLong, jobBias)); + assertFalse(isJobRestricted(jc.ui, jobBias)); + assertFalse(isJobRestricted(jc.uiRetried, jobBias)); + assertFalse(isJobRestricted(jc.uiRunning, jobBias)); + assertFalse(isJobRestricted(jc.uiRunningLong, jobBias)); + } + } - final JobStatus jobMinPriority = createJobStatus("testIsJobRestricted", - createJobBuilder(1).setPriority(JobInfo.PRIORITY_MIN).build()); - final JobStatus jobLowPriority = createJobStatus("testIsJobRestricted", - createJobBuilder(2).setPriority(JobInfo.PRIORITY_LOW).build()); - final JobStatus jobLowPriorityRunning = createJobStatus("testIsJobRestricted", - createJobBuilder(3).setPriority(JobInfo.PRIORITY_LOW).build()); - final JobStatus jobLowPriorityRunningLong = createJobStatus("testIsJobRestricted", - createJobBuilder(9).setPriority(JobInfo.PRIORITY_LOW).build()); - final JobStatus jobDefaultPriority = createJobStatus("testIsJobRestricted", - createJobBuilder(4).setPriority(JobInfo.PRIORITY_DEFAULT).build()); - final JobStatus jobHighPriority = createJobStatus("testIsJobRestricted", - createJobBuilder(5).setPriority(JobInfo.PRIORITY_HIGH).build()); - final JobStatus jobHighPriorityRunning = createJobStatus("testIsJobRestricted", - createJobBuilder(6).setPriority(JobInfo.PRIORITY_HIGH).build()); - final JobStatus jobHighPriorityRunningLong = createJobStatus("testIsJobRestricted", - createJobBuilder(10).setPriority(JobInfo.PRIORITY_HIGH).build()); - final JobStatus ejDowngraded = createJobStatus("testIsJobRestricted", - createJobBuilder(7).setExpedited(true).build()); - final JobStatus ej = spy(createJobStatus("testIsJobRestricted", - createJobBuilder(8).setExpedited(true).build())); - final JobStatus ejRetried = spy(createJobStatus("testIsJobRestricted", - createJobBuilder(11).setExpedited(true).build())); - final JobStatus ejRunning = spy(createJobStatus("testIsJobRestricted", - createJobBuilder(12).setExpedited(true).build())); - final JobStatus ejRunningLong = spy(createJobStatus("testIsJobRestricted", - createJobBuilder(13).setExpedited(true).build())); - final JobStatus ui = spy(createJobStatus("testIsJobRestricted", - createJobBuilder(14).build())); - final JobStatus uiRetried = spy(createJobStatus("testIsJobRestricted", - createJobBuilder(15).build())); - final JobStatus uiRunning = spy(createJobStatus("testIsJobRestricted", - createJobBuilder(16).build())); - final JobStatus uiRunningLong = spy(createJobStatus("testIsJobRestricted", - createJobBuilder(17).build())); - when(ej.shouldTreatAsExpeditedJob()).thenReturn(true); - when(ejRetried.shouldTreatAsExpeditedJob()).thenReturn(true); - when(ejRunning.shouldTreatAsExpeditedJob()).thenReturn(true); - when(ejRunningLong.shouldTreatAsExpeditedJob()).thenReturn(true); - when(ui.shouldTreatAsUserInitiatedJob()).thenReturn(true); - when(uiRetried.shouldTreatAsUserInitiatedJob()).thenReturn(true); - when(uiRunning.shouldTreatAsUserInitiatedJob()).thenReturn(true); - when(uiRunningLong.shouldTreatAsUserInitiatedJob()).thenReturn(true); - when(ejRetried.getNumPreviousAttempts()).thenReturn(1); - when(uiRetried.getNumPreviousAttempts()).thenReturn(2); - when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)).thenReturn(true); - when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning)) - .thenReturn(true); - when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunningLong)) - .thenReturn(true); - when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunningLong)) - .thenReturn(true); - when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunning)).thenReturn(true); - when(mJobSchedulerService.isCurrentlyRunningLocked(ejRunningLong)).thenReturn(true); - when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunning)).thenReturn(true); - when(mJobSchedulerService.isCurrentlyRunningLocked(uiRunningLong)).thenReturn(true); - when(mJobSchedulerService.isJobInOvertimeLocked(jobLowPriorityRunningLong)) - .thenReturn(true); - when(mJobSchedulerService.isJobInOvertimeLocked(jobHighPriorityRunningLong)) - .thenReturn(true); - when(mJobSchedulerService.isJobInOvertimeLocked(ejRunningLong)).thenReturn(true); - when(mJobSchedulerService.isJobInOvertimeLocked(uiRunningLong)).thenReturn(true); - - assertFalse(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ej)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejRetried)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunningLong)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ui)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong)); - - mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_LIGHT); - - assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ej)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejRetried)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunningLong)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ui)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong)); - - mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_MODERATE); - - assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); - assertFalse(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ej)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong)); - assertFalse(mThermalStatusRestriction.isJobRestricted(ui)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning)); - assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong)); - - mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE); + /** + * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is Top App and all + * Thermal states. + */ + @Test + public void testIsJobRestrictedBiasTopApp() { + JobStatusContainer jc = + new JobStatusContainer("testIsJobRestrictedBiasTopApp", mJobSchedulerService); + + int jobBias = JobInfo.BIAS_TOP_APP; + for (int thermalStatus : jc.thermalStatuses) { + String msg = "Thermal Status = " + DebugUtils.valueToString( + PowerManager.class, "THERMAL_STATUS_", thermalStatus); + mStatusChangedListener.onThermalStatusChanged(thermalStatus); + + // No restrictions on any jobs + assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.importantWhileForeground, jobBias)); + assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.ej, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.ui, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + } + } - assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ej)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ui)); - assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried)); - assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong)); + /** + * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is Foreground + * Service and all Thermal states. + */ + @Test + @RequiresFlagsDisabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS) + public void testIsJobRestrictedBiasFgs_flagThermalRestrictionsToFgsJobsDisabled() { + JobStatusContainer jc = + new JobStatusContainer("testIsJobRestrictedBiasFgs", mJobSchedulerService); + + int jobBias = JobInfo.BIAS_FOREGROUND_SERVICE; + for (int thermalStatus : jc.thermalStatuses) { + String msg = "Thermal Status = " + DebugUtils.valueToString( + PowerManager.class, "THERMAL_STATUS_", thermalStatus); + mStatusChangedListener.onThermalStatusChanged(thermalStatus); + // No restrictions on any jobs + assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.ej, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.ui, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + } + } - mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_CRITICAL); + /** + * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is Foreground + * Service and all Thermal states. + */ + @Test + @RequiresFlagsEnabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS) + public void testIsJobRestrictedBiasFgs_flagThermalRestrictionsToFgsJobsEnabled() { + JobStatusContainer jc = + new JobStatusContainer("testIsJobRestrictedBiasFgs", mJobSchedulerService); + int jobBias = JobInfo.BIAS_FOREGROUND_SERVICE; + for (int thermalStatus : jc.thermalStatuses) { + String msg = debugTag(jobBias, thermalStatus); + mStatusChangedListener.onThermalStatusChanged(thermalStatus); + if (thermalStatus >= THERMAL_STATUS_SEVERE) { + // Full restrictions on all jobs + assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + assertTrue(msg, isJobRestricted(jc.ej, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + assertTrue(msg, isJobRestricted(jc.ui, jobBias)); + assertTrue(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertTrue(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + } else if (thermalStatus >= THERMAL_STATUS_MODERATE) { + // No restrictions on user related jobs + assertFalse(msg, isJobRestricted(jc.ui, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + // Some restrictions on expedited jobs + assertFalse(msg, isJobRestricted(jc.ej, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + // Some restrictions on high priority jobs + assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + // Some restructions on important while foreground jobs + assertFalse(isJobRestricted(jc.importantWhileForeground, jobBias)); + assertFalse(isJobRestricted(jc.importantWhileForegroundRunning, jobBias)); + assertTrue(isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias)); + // Full restriction on default priority jobs + assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + // Full restriction on low priority jobs + assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + // Full restriction on min priority jobs + assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + } else { + // thermalStatus < THERMAL_STATUS_MODERATE + // No restrictions on any job type + assertFalse(msg, isJobRestricted(jc.ui, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.ej, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.importantWhileForeground, jobBias)); + assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.importantWhileForegroundRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + } + } + } - assertTrue(mThermalStatusRestriction.isJobRestricted(jobMinPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobLowPriorityRunningLong)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobDefaultPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriority)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(jobHighPriorityRunningLong)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejDowngraded)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ej)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejRetried)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong)); - assertTrue(mThermalStatusRestriction.isJobRestricted(ui)); - assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried)); - assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong)); + /** + * Test {@link JobSchedulerService#isJobRestricted(JobStatus)} when Job Bias is less than + * Foreground Service and all Thermal states. + */ + @Test + public void testIsJobRestrictedBiasLessThanFgs() { + JobStatusContainer jc = + new JobStatusContainer("testIsJobRestrictedBiasLessThanFgs", mJobSchedulerService); + + for (int jobBias : jc.biasesBelowFgs) { + for (int thermalStatus : jc.thermalStatuses) { + String msg = debugTag(jobBias, thermalStatus); + mStatusChangedListener.onThermalStatusChanged(thermalStatus); + if (thermalStatus >= THERMAL_STATUS_SEVERE) { + // Full restrictions on all jobs + assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + assertTrue(msg, isJobRestricted(jc.ej, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + assertTrue(msg, isJobRestricted(jc.ui, jobBias)); + assertTrue(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertTrue(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + } else if (thermalStatus >= THERMAL_STATUS_MODERATE) { + // No restrictions on user related jobs + assertFalse(msg, isJobRestricted(jc.ui, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + // Some restrictions on expedited jobs + assertFalse(msg, isJobRestricted(jc.ej, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + // Some restrictions on high priority jobs + assertTrue(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + // Full restriction on default priority jobs + assertTrue(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + // Full restriction on low priority jobs + assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + // Full restriction on min priority jobs + assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + } else if (thermalStatus >= THERMAL_STATUS_LIGHT) { + // No restrictions on any user related jobs + assertFalse(msg, isJobRestricted(jc.ui, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + // No restrictions on any expedited jobs + assertFalse(msg, isJobRestricted(jc.ej, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + // No restrictions on any high priority jobs + assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + // No restrictions on default priority jobs + assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + // Some restrictions on low priority jobs + assertTrue(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertTrue(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + // Full restriction on min priority jobs + assertTrue(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + } else { // THERMAL_STATUS_NONE + // No restrictions on any jobs + assertFalse(msg, isJobRestricted(jc.jobMinPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobLowPriorityRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobDefaultPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriority, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.jobHighPriorityRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.ej, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejDowngraded, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.ejRunningLong, jobBias)); + assertFalse(msg, isJobRestricted(jc.ui, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRetried, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunning, jobBias)); + assertFalse(msg, isJobRestricted(jc.uiRunningLong, jobBias)); + } + } + } } private JobInfo.Builder createJobBuilder(int jobId) { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index c9aab5318840..396edae2f672 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -186,7 +186,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { class Mocks { val lock = PackageManagerTracedLock() - val installLock = Any() + val installLock = PackageManagerTracedLock() val injector: PackageManagerServiceInjector = mock() val systemWrapper: PackageManagerServiceInjector.SystemWrapper = mock() val context: Context = mock() diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java index 29f3720a1828..1b0a8d2222b9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperCropperTest.java @@ -210,12 +210,10 @@ public class WallpaperCropperTest { new Rect(0, 0, bitmapSize.x, bitmapSize.y), new Rect(100, 200, bitmapSize.x - 100, bitmapSize.y))) { for (int mode: ALL_MODES) { - for (boolean rtl: List.of(true, false)) { - for (boolean parallax: List.of(true, false)) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, parallax, rtl, mode)) - .isEqualTo(crop); - } + for (boolean parallax: List.of(true, false)) { + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, parallax, mode)) + .isEqualTo(crop); } } } @@ -235,11 +233,9 @@ public class WallpaperCropperTest { int expectedWidth = (int) (displaySize.x * (1 + WallpaperCropper.MAX_PARALLAX)); Point expectedCropSize = new Point(expectedWidth, 1000); for (int mode: ALL_MODES) { - for (boolean rtl: List.of(false, true)) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, true, rtl, mode)) - .isEqualTo(centerOf(crop, expectedCropSize)); - } + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, true, mode)) + .isEqualTo(centerOf(crop, expectedCropSize)); } } @@ -258,11 +254,9 @@ public class WallpaperCropperTest { Point bitmapSize = new Point(acceptableWidth, 1000); Rect crop = new Rect(0, 0, bitmapSize.x, bitmapSize.y); for (int mode : ALL_MODES) { - for (boolean rtl : List.of(false, true)) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, true, rtl, mode)) - .isEqualTo(crop); - } + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, true, mode)) + .isEqualTo(crop); } } } @@ -292,11 +286,9 @@ public class WallpaperCropperTest { for (int i = 0; i < crops.size(); i++) { Rect crop = crops.get(i); Rect expectedCrop = expectedAdjustedCrops.get(i); - for (boolean rtl: List.of(false, true)) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.ADD)) - .isEqualTo(expectedCrop); - } + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, false, WallpaperCropper.ADD)) + .isEqualTo(expectedCrop); } } @@ -317,11 +309,9 @@ public class WallpaperCropperTest { Point expectedCropSize = new Point(1000, 1000); for (Rect crop: crops) { - for (boolean rtl : List.of(false, true)) { - assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, false, rtl, WallpaperCropper.REMOVE)) - .isEqualTo(centerOf(crop, expectedCropSize)); - } + assertThat(WallpaperCropper.getAdjustedCrop( + crop, bitmapSize, displaySize, false, WallpaperCropper.REMOVE)) + .isEqualTo(centerOf(crop, expectedCropSize)); } } @@ -348,14 +338,14 @@ public class WallpaperCropperTest { Rect crop = crops.get(i); Rect expected = expectedAdjustedCrops.get(i); assertThat(WallpaperCropper.getAdjustedCrop( - crop, bitmapSize, displaySize, false, false, WallpaperCropper.BALANCE)) + crop, bitmapSize, displaySize, false, WallpaperCropper.BALANCE)) .isEqualTo(expected); Rect transposedCrop = new Rect(crop.top, crop.left, crop.bottom, crop.right); Rect expectedTransposed = new Rect( expected.top, expected.left, expected.bottom, expected.right); assertThat(WallpaperCropper.getAdjustedCrop(transposedCrop, bitmapSize, - transposedDisplaySize, false, false, WallpaperCropper.BALANCE)) + transposedDisplaySize, false, WallpaperCropper.BALANCE)) .isEqualTo(expectedTransposed); } } diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index a9ff3a133f6e..4460c6af0691 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -22,10 +22,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -52,6 +54,7 @@ import com.android.internal.app.IBatteryStats; import com.android.server.LocalServices; import com.android.server.policy.WindowManagerPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; +import com.android.server.power.feature.PowerManagerFlags; import com.android.server.statusbar.StatusBarManagerInternal; import org.junit.Before; @@ -77,6 +80,9 @@ public class NotifierTest { @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock; @Mock private Vibrator mVibrator; @Mock private StatusBarManagerInternal mStatusBarManagerInternal; + @Mock private WakeLockLog mWakeLockLog; + + @Mock private PowerManagerFlags mPowerManagerFlags; private PowerManagerService mService; private Context mContextSpy; @@ -222,6 +228,7 @@ public class NotifierTest { @Test public void testOnWakeLockListener_RemoteException_NoRethrow() { + when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true); createNotifier(); IWakeLockCallback exceptingCallback = new IWakeLockCallback.Stub() { @@ -235,6 +242,9 @@ public class NotifierTest { mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null, exceptingCallback); + verifyZeroInteractions(mWakeLockLog); + mTestLooper.dispatchAll(); + verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, 1); mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null, exceptingCallback); @@ -244,8 +254,27 @@ public class NotifierTest { PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "wakelockTag", "my.package.name", uid, pid, /* newWorkSource= */ null, /* newHistoryTag= */ null, exceptingCallback); + verifyNoMoreInteractions(mWakeLockLog); mTestLooper.dispatchAll(); + verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid, + PowerManager.PARTIAL_WAKE_LOCK, 1); // If we didn't throw, we're good! + + // Test with improveWakelockLatency flag false, hence the wakelock log will run on the same + // thread + clearInvocations(mWakeLockLog); + when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(false); + + mNotifier.onWakeLockAcquired(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", + "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null, + exceptingCallback); + verify(mWakeLockLog).onWakeLockAcquired("wakelockTag", uid, + PowerManager.PARTIAL_WAKE_LOCK, -1); + + mNotifier.onWakeLockReleased(PowerManager.PARTIAL_WAKE_LOCK, "wakelockTag", + "my.package.name", uid, pid, /* workSource= */ null, /* historyTag= */ null, + exceptingCallback); + verify(mWakeLockLog).onWakeLockReleased("wakelockTag", uid, -1); } private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() { @@ -253,7 +282,7 @@ public class NotifierTest { Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, SuspendBlocker suspendBlocker, WindowManagerPolicy policy, FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector, - Executor backgroundExecutor) { + Executor backgroundExecutor, PowerManagerFlags powerManagerFlags) { return mNotifierMock; } @@ -326,6 +355,18 @@ public class NotifierTest { } private void createNotifier() { + Notifier.Injector injector = new Notifier.Injector() { + @Override + public long currentTimeMillis() { + return 1; + } + + @Override + public WakeLockLog getWakeLockLog(Context context) { + return mWakeLockLog; + } + }; + mNotifier = new Notifier( mTestLooper.getLooper(), mContextSpy, @@ -335,7 +376,7 @@ public class NotifierTest { null, null, null, - mTestExecutor); + mTestExecutor, mPowerManagerFlags, injector); } private static class FakeExecutor implements Executor { diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java index 7f165e0e8885..b737e0f98317 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java @@ -114,6 +114,7 @@ import com.android.server.power.PowerManagerService.WakeLock; import com.android.server.power.batterysaver.BatterySaverController; import com.android.server.power.batterysaver.BatterySaverPolicy; import com.android.server.power.batterysaver.BatterySaverStateMachine; +import com.android.server.power.feature.PowerManagerFlags; import com.android.server.testutils.OffsettableClock; import com.google.testing.junit.testparameterinjector.TestParameter; @@ -275,7 +276,7 @@ public class PowerManagerServiceTest { Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats, SuspendBlocker suspendBlocker, WindowManagerPolicy policy, FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector, - Executor executor) { + Executor executor, PowerManagerFlags powerManagerFlags) { return mNotifierMock; } diff --git a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java index 0fad25d35515..1c4db6ad883b 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/WakeLockLogTest.java @@ -57,19 +57,42 @@ public class WakeLockLogTest { } @Test - public void testAddTwoItems() { + public void testAddTwoItems_withNoEventTimeSupplied() { final int tagDatabaseSize = 128; final int logSize = 20; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, - PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, -1); when(injectorSpy.currentTimeMillis()).thenReturn(1150L); log.onWakeLockAcquired("TagFull", 102, - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, -1); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial " + + "(partial,on-after-release)\n" + + " 01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull " + + "(full,acq-causes-wake)\n" + + " -\n" + + " Events: 2, Time-Resets: 0\n" + + " Buffer, Bytes used: 6\n", + dumpLog(log, false)); + } + + @Test + public void testAddTwoItems() { + final int tagDatabaseSize = 128; + final int logSize = 20; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); + + log.onWakeLockAcquired("TagPartial", 101, + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L); + + log.onWakeLockAcquired("TagFull", 102, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1150L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial " @@ -89,11 +112,9 @@ public class WakeLockLogTest { TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); - log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); + log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L); - when(injectorSpy.currentTimeMillis()).thenReturn(1350L); - log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK); + log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK, 1350L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial (partial)\n" @@ -111,11 +132,9 @@ public class WakeLockLogTest { TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); - log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); + log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L); - when(injectorSpy.currentTimeMillis()).thenReturn(1150L); - log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK); + log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK, 1150L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - --- - ACQ UNKNOWN (partial)\n" @@ -134,41 +153,33 @@ public class WakeLockLogTest { WakeLockLog log = new WakeLockLog(injectorSpy, mContext); // Wake lock 1 acquired - log size = 3 - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); - log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); + log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L); // Wake lock 2 acquired - log size = 3 + 3 = 6 - when(injectorSpy.currentTimeMillis()).thenReturn(1150L); - log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK); + log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK, 1150L); // Wake lock 3 acquired - log size = 6 + 3 = 9 - when(injectorSpy.currentTimeMillis()).thenReturn(1151L); - log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK); + log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK, 1151L); // We need more space - wake lock 1 acquisition is removed from the log and saved in the // list. Log size = 9 - 3 + 2 = 8 - when(injectorSpy.currentTimeMillis()).thenReturn(1152L); - log.onWakeLockReleased("TagThree", 101); + log.onWakeLockReleased("TagThree", 101, 1152L); // We need more space - wake lock 2 acquisition is removed from the log and saved in the // list. Log size = 8 - 3 + 2 = 7 - when(injectorSpy.currentTimeMillis()).thenReturn(1153L); - log.onWakeLockReleased("TagPartial", 101); + log.onWakeLockReleased("TagPartial", 101, 1153L); // We need more space - wake lock 3 acquisition is removed from the log and saved in the // list. Log size = 7 - 3 + 3 = 7 - when(injectorSpy.currentTimeMillis()).thenReturn(1154L); - log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK); + log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK, 1154L); // We need more space - wake lock 3 release is removed from the log and wake lock 3 // acquisition is removed from the list. Log size = 7 - 2 + 3 = 8 - when(injectorSpy.currentTimeMillis()).thenReturn(1155L); - log.onWakeLockAcquired("TagFive", 101, PowerManager.PARTIAL_WAKE_LOCK); + log.onWakeLockAcquired("TagFive", 101, PowerManager.PARTIAL_WAKE_LOCK, 1155L); // We need more space - wake lock 1 release is removed from the log and wake lock 1 // acquisition is removed from the list. Log size = 8 - 2 + 2 = 8 - when(injectorSpy.currentTimeMillis()).thenReturn(1156L); - log.onWakeLockReleased("TagFull", 102); + log.onWakeLockReleased("TagFull", 102, 1156L); // Wake lock 2 acquisition is still printed because its release have not rolled off the log // yet. @@ -191,8 +202,8 @@ public class WakeLockLogTest { WakeLockLog log = new WakeLockLog(injectorSpy, mContext); // Bad tag means it wont get written - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); - log.onWakeLockAcquired(null /* tag */, 0 /* ownerUid */, PowerManager.PARTIAL_WAKE_LOCK); + log.onWakeLockAcquired( + null /* tag */, 0 /* ownerUid */, PowerManager.PARTIAL_WAKE_LOCK, 1000L); assertEquals("Wake Lock Log\n" + " -\n" @@ -208,9 +219,8 @@ public class WakeLockLogTest { TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101, - PowerManager.PARTIAL_WAKE_LOCK); + PowerManager.PARTIAL_WAKE_LOCK, 1000L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ " @@ -228,10 +238,8 @@ public class WakeLockLogTest { TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); - log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK); - when(injectorSpy.currentTimeMillis()).thenReturn(1001L); - log.onWakeLockReleased("HowdyTag", 101); + log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1000L); + log.onWakeLockReleased("HowdyTag", 101, 1001L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ HowdyTag (partial)\n" @@ -250,12 +258,10 @@ public class WakeLockLogTest { TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1100L); - log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK); + log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK, 1100L); // New element goes back in time...should not be written to log. - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); - log.onWakeLockReleased("HowdyTag", 101); + log.onWakeLockReleased("HowdyTag", 101, 1000L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.100 - 101 (some.package1) - ACQ HowdyTag (partial)\n" @@ -272,9 +278,8 @@ public class WakeLockLogTest { TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, - PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK); + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK, 1000L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial " @@ -293,9 +298,8 @@ public class WakeLockLogTest { WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(mPackageManager.getPackagesForUid(101)).thenReturn(null); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, - PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - 101 - ACQ TagPartial " @@ -316,9 +320,8 @@ public class WakeLockLogTest { when(mPackageManager.getPackagesForUid(101)).thenReturn( new String[]{ "some.package1", "some.package2", "some.package3" }); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, - PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - 101 (some.package1,...) - ACQ TagPartial " @@ -336,17 +339,14 @@ public class WakeLockLogTest { TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, - PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L); - when(injectorSpy.currentTimeMillis()).thenReturn(1150L); log.onWakeLockAcquired("TagFull", 101, - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1150L); - when(injectorSpy.currentTimeMillis()).thenReturn(1151L); log.onWakeLockAcquired("TagFull2", 101, - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1151L); assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial " @@ -370,29 +370,23 @@ public class WakeLockLogTest { TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, - PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, 1000L); - when(injectorSpy.currentTimeMillis()).thenReturn(1150L); log.onWakeLockAcquired("TagFull", 101, - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1150L); - when(injectorSpy.currentTimeMillis()).thenReturn(1151L); log.onWakeLockAcquired("TagFull2", 101, - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1151L); - when(injectorSpy.currentTimeMillis()).thenReturn(1152L); log.onWakeLockAcquired("TagFull3", 101, - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1152L); - when(injectorSpy.currentTimeMillis()).thenReturn(1153L); log.onWakeLockAcquired("TagFull4", 101, - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1153L); - when(injectorSpy.currentTimeMillis()).thenReturn(1154L); log.onWakeLockAcquired("TagFull5", 101, - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, 1154L); // The first 3 events have been removed from the log and they exist in the saved // acquisitions list. They should also use the cache when fetching the package names. diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java index d29bf1abd7a3..3635e9a749e2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java @@ -19,6 +19,7 @@ package com.android.server.power.stats; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.content.Context; import android.os.BatteryManager; @@ -49,9 +50,9 @@ public class BatteryStatsResetTest { private static final int BATTERY_CHARGE_RATE_SECONDS_PER_LEVEL = 100; private MockClock mMockClock; + private BatteryStatsImpl.BatteryStatsConfig mConfig; private MockBatteryStatsImpl mBatteryStatsImpl; - /** * Battery status. Must be one of the following: * {@link BatteryManager#BATTERY_STATUS_UNKNOWN} @@ -91,8 +92,9 @@ public class BatteryStatsResetTest { @Before public void setUp() throws IOException { + mConfig = mock(BatteryStatsImpl.BatteryStatsConfig.class); mMockClock = new MockClock(); - mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock, + mBatteryStatsImpl = new MockBatteryStatsImpl(mConfig, mMockClock, Files.createTempDirectory("BatteryStatsResetTest").toFile()); mBatteryStatsImpl.onSystemReady(mock(Context.class)); @@ -110,10 +112,7 @@ public class BatteryStatsResetTest { @Test public void testResetOnUnplug_highBatteryLevel() { - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugHighBatteryLevel(true) - .build()); + when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(true); long expectedResetTimeUs = 0; @@ -137,10 +136,7 @@ public class BatteryStatsResetTest { assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs); // disable high battery level reset on unplug. - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugHighBatteryLevel(false) - .build()); + when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false); dischargeToLevel(60); @@ -153,10 +149,7 @@ public class BatteryStatsResetTest { @Test public void testResetOnUnplug_significantCharge() { - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugAfterSignificantCharge(true) - .build()); + when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(true); long expectedResetTimeUs = 0; unplugBattery(); @@ -186,10 +179,7 @@ public class BatteryStatsResetTest { assertThat(mBatteryStatsImpl.getStatsStartRealtime()).isEqualTo(expectedResetTimeUs); // disable reset on unplug after significant charge. - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugAfterSignificantCharge(false) - .build()); + when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false); // Battery level dropped below 20%. dischargeToLevel(15); @@ -256,11 +246,9 @@ public class BatteryStatsResetTest { @Test public void testResetWhilePluggedIn_longPlugIn() { // disable high battery level reset on unplug. - mBatteryStatsImpl.setBatteryStatsConfig( - new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setResetOnUnplugHighBatteryLevel(false) - .setResetOnUnplugAfterSignificantCharge(false) - .build()); + when(mConfig.shouldResetOnUnplugHighBatteryLevel()).thenReturn(false); + when(mConfig.shouldResetOnUnplugAfterSignificantCharge()).thenReturn(false); + long expectedResetTimeUs = 0; plugBattery(BatteryManager.BATTERY_PLUGGED_USB); diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 2d7cb2245c0a..6edfedee9e5b 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -98,10 +98,12 @@ public class BatteryUsageStatsRule implements TestRule { mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000}); mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000}); mBatteryStatsConfigBuilder = new BatteryStatsImpl.BatteryStatsConfig.Builder() - .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_CPU, - 10000) - .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, - 10000); + .setPowerStatsThrottlePeriodMillis( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_CPU), 10000) + .setPowerStatsThrottlePeriodMillis( + BatteryConsumer.powerComponentIdToString( + BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO), 10000); } private void initBatteryStats() { @@ -290,7 +292,8 @@ public class BatteryUsageStatsRule implements TestRule { public BatteryUsageStatsRule setPowerStatsThrottlePeriodMillis(int powerComponent, long throttleMs) { - mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis(powerComponent, throttleMs); + mBatteryStatsConfigBuilder.setPowerStatsThrottlePeriodMillis( + BatteryConsumer.powerComponentIdToString(powerComponent), throttleMs); return this; } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsCollectorTest.java new file mode 100644 index 000000000000..02c7b745b24c --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsCollectorTest.java @@ -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.server.power.stats; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.bluetooth.BluetoothActivityEnergyInfo; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.UidTraffic; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.Handler; +import android.os.Parcel; +import android.platform.test.ravenwood.RavenwoodRule; +import android.util.IndentingPrintWriter; +import android.util.SparseLongArray; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerStats; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.IntSupplier; + +public class BluetoothPowerStatsCollectorTest { + private static final int APP_UID1 = 42; + private static final int APP_UID2 = 24; + private static final int ISOLATED_UID = 99123; + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setPowerStatsThrottlePeriodMillis(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, 1000); + + private MockBatteryStatsImpl mBatteryStats; + + private final MockClock mClock = mStatsRule.getMockClock(); + + @Mock + private Context mContext; + @Mock + private PackageManager mPackageManager; + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver(); + + private BluetoothActivityEnergyInfo mBluetoothActivityEnergyInfo; + private final SparseLongArray mUidScanTimes = new SparseLongArray(); + + private final BluetoothPowerStatsCollector.BluetoothStatsRetriever mBluetoothStatsRetriever = + new BluetoothPowerStatsCollector.BluetoothStatsRetriever() { + @Override + public void retrieveBluetoothScanTimes(Callback callback) { + for (int i = 0; i < mUidScanTimes.size(); i++) { + callback.onBluetoothScanTime(mUidScanTimes.keyAt(i), + mUidScanTimes.valueAt(i)); + } + } + + @Override + public boolean requestControllerActivityEnergyInfo(Executor executor, + BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback) { + callback.onBluetoothActivityEnergyInfoAvailable(mBluetoothActivityEnergyInfo); + return true; + } + }; + + private final List<PowerStats> mRecordedPowerStats = new ArrayList<>(); + + private BluetoothPowerStatsCollector.Injector mInjector = + new BluetoothPowerStatsCollector.Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> 3500; + } + + @Override + public BluetoothPowerStatsCollector.BluetoothStatsRetriever + getBluetoothStatsRetriever() { + return mBluetoothStatsRetriever; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)).thenReturn(true); + mPowerStatsUidResolver.noteIsolatedUidAdded(ISOLATED_UID, APP_UID2); + mBatteryStats = mStatsRule.getBatteryStats(); + } + + @SuppressWarnings("GuardedBy") + @Test + public void triggering() throws Throwable { + PowerStatsCollector collector = mBatteryStats.getPowerStatsCollector( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH); + collector.addConsumer(mRecordedPowerStats::add); + + mBatteryStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_BLUETOOTH, + true); + + mBatteryStats.setDummyExternalStatsSync(new MockBatteryStatsImpl.DummyExternalStatsSync(){ + @Override + public void scheduleSyncDueToProcessStateChange(int flags, long delayMillis) { + collector.schedule(); + } + }); + + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1000, 2000, 3000, 600); + + // This should trigger a sample collection to establish a baseline + mBatteryStats.onSystemReady(mContext); + + mStatsRule.waitForBackgroundThread(); + assertThat(mRecordedPowerStats).hasSize(1); + + mRecordedPowerStats.clear(); + mStatsRule.setTime(70000, 70000); + mBatteryStats.noteUidProcessStateLocked(APP_UID1, ActivityManager.PROCESS_STATE_TOP, + mClock.realtime, mClock.uptime); + mStatsRule.waitForBackgroundThread(); + assertThat(mRecordedPowerStats).hasSize(1); + } + + @Test + public void collectStats() { + PowerStats powerStats = collectPowerStats(); + assertThat(powerStats.durationMs).isEqualTo(7200); + + BluetoothPowerStatsLayout layout = new BluetoothPowerStatsLayout(powerStats.descriptor); + assertThat(layout.getDeviceRxTime(powerStats.stats)).isEqualTo(6000); + assertThat(layout.getDeviceTxTime(powerStats.stats)).isEqualTo(1000); + assertThat(layout.getDeviceIdleTime(powerStats.stats)).isEqualTo(200); + assertThat(layout.getDeviceScanTime(powerStats.stats)).isEqualTo(800); + assertThat(layout.getConsumedEnergy(powerStats.stats, 0)) + .isEqualTo((64321 - 10000) * 1000 / 3500); + + assertThat(powerStats.uidStats.size()).isEqualTo(2); + long[] actual1 = powerStats.uidStats.get(APP_UID1); + assertThat(layout.getUidRxBytes(actual1)).isEqualTo(1000); + assertThat(layout.getUidTxBytes(actual1)).isEqualTo(2000); + assertThat(layout.getUidScanTime(actual1)).isEqualTo(100); + + // Combines APP_UID2 and ISOLATED_UID + long[] actual2 = powerStats.uidStats.get(APP_UID2); + assertThat(layout.getUidRxBytes(actual2)).isEqualTo(8000); + assertThat(layout.getUidTxBytes(actual2)).isEqualTo(10000); + assertThat(layout.getUidScanTime(actual2)).isEqualTo(700); + + assertThat(powerStats.uidStats.get(ISOLATED_UID)).isNull(); + } + + @Test + public void dump() throws Throwable { + PowerStats powerStats = collectPowerStats(); + StringWriter sw = new StringWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(sw); + powerStats.dump(pw); + pw.flush(); + String dump = sw.toString(); + assertThat(dump).contains("duration=7200"); + assertThat(dump).contains( + "rx: 6000 tx: 1000 idle: 200 scan: 800 energy: " + ((64321 - 10000) * 1000 / 3500)); + assertThat(dump).contains("UID 24: rx-B: 8000 tx-B: 10000 scan: 700"); + assertThat(dump).contains("UID 42: rx-B: 1000 tx-B: 2000 scan: 100"); + } + + private PowerStats collectPowerStats() { + BluetoothPowerStatsCollector collector = new BluetoothPowerStatsCollector(mInjector); + collector.setEnabled(true); + + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH)) + .thenReturn(new int[]{777}); + + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1000, 600, 100, 2000, + mockUidTraffic(APP_UID1, 100, 200), + mockUidTraffic(APP_UID2, 300, 400), + mockUidTraffic(ISOLATED_UID, 500, 600)); + + mUidScanTimes.put(APP_UID1, 100); + + when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777}))) + .thenReturn(new long[]{10000}); + + // Establish a baseline + collector.collectStats(); + + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1100, 6600, 1100, 2200, + mockUidTraffic(APP_UID1, 1100, 2200), + mockUidTraffic(APP_UID2, 3300, 4400), + mockUidTraffic(ISOLATED_UID, 5500, 6600)); + + mUidScanTimes.clear(); + mUidScanTimes.put(APP_UID1, 200); + mUidScanTimes.put(APP_UID2, 300); + mUidScanTimes.put(ISOLATED_UID, 400); + + when(mConsumedEnergyRetriever.getConsumedEnergyUws(eq(new int[]{777}))) + .thenReturn(new long[]{64321}); + + mStatsRule.setTime(20000, 20000); + return collector.collectStats(); + } + + private BluetoothActivityEnergyInfo mockBluetoothActivityEnergyInfo(long timestamp, + long rxTimeMs, long txTimeMs, long idleTimeMs, UidTraffic... uidTraffic) { + if (RavenwoodRule.isOnRavenwood()) { + BluetoothActivityEnergyInfo info = mock(BluetoothActivityEnergyInfo.class); + when(info.getControllerRxTimeMillis()).thenReturn(rxTimeMs); + when(info.getControllerTxTimeMillis()).thenReturn(txTimeMs); + when(info.getControllerIdleTimeMillis()).thenReturn(idleTimeMs); + when(info.getUidTraffic()).thenReturn(List.of(uidTraffic)); + return info; + } else { + final Parcel btActivityEnergyInfoParcel = Parcel.obtain(); + btActivityEnergyInfoParcel.writeLong(timestamp); + btActivityEnergyInfoParcel.writeInt( + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE); + btActivityEnergyInfoParcel.writeLong(txTimeMs); + btActivityEnergyInfoParcel.writeLong(rxTimeMs); + btActivityEnergyInfoParcel.writeLong(idleTimeMs); + btActivityEnergyInfoParcel.writeLong(0L); + btActivityEnergyInfoParcel.writeTypedList(List.of(uidTraffic)); + btActivityEnergyInfoParcel.setDataPosition(0); + + BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR + .createFromParcel(btActivityEnergyInfoParcel); + btActivityEnergyInfoParcel.recycle(); + return info; + } + } + + private UidTraffic mockUidTraffic(int uid, long rxBytes, long txBytes) { + if (RavenwoodRule.isOnRavenwood()) { + UidTraffic traffic = mock(UidTraffic.class); + when(traffic.getUid()).thenReturn(uid); + when(traffic.getRxBytes()).thenReturn(rxBytes); + when(traffic.getTxBytes()).thenReturn(txBytes); + return traffic; + } else { + final Parcel uidTrafficParcel = Parcel.obtain(); + uidTrafficParcel.writeInt(uid); + uidTrafficParcel.writeLong(rxBytes); + uidTrafficParcel.writeLong(txBytes); + uidTrafficParcel.setDataPosition(0); + + UidTraffic traffic = UidTraffic.CREATOR.createFromParcel(uidTrafficParcel); + uidTrafficParcel.recycle(); + return traffic; + } + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java new file mode 100644 index 000000000000..752bc2712fe2 --- /dev/null +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerStatsProcessorTest.java @@ -0,0 +1,540 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.power.stats; + +import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND; +import static android.os.BatteryConsumer.PROCESS_STATE_CACHED; +import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND; +import static android.os.BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE; + +import static com.android.server.power.stats.AggregatedPowerStatsConfig.POWER_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_ON; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.SCREEN_STATE_OTHER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_POWER; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_PROCESS_STATE; +import static com.android.server.power.stats.AggregatedPowerStatsConfig.STATE_SCREEN; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothActivityEnergyInfo; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.UidTraffic; +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.power.stats.EnergyConsumerType; +import android.os.BatteryConsumer; +import android.os.Handler; +import android.os.Parcel; +import android.os.Process; +import android.platform.test.ravenwood.RavenwoodRule; +import android.util.SparseLongArray; + +import com.android.internal.os.Clock; +import com.android.internal.os.PowerProfile; +import com.android.server.power.stats.BluetoothPowerStatsCollector.BluetoothStatsRetriever; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.IntSupplier; + +public class BluetoothPowerStatsProcessorTest { + + @Rule(order = 0) + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .setProvideMainThread(true) + .build(); + + private static final double PRECISION = 0.00001; + private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42; + private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101; + private static final int BLUETOOTH_ENERGY_CONSUMER_ID = 1; + private static final int VOLTAGE_MV = 3500; + + @Rule(order = 1) + public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() + .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_RX, 50.0) + .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_TX, 100.0) + .setAveragePower(PowerProfile.POWER_BLUETOOTH_CONTROLLER_IDLE, 10.0) + .initMeasuredEnergyStatsLocked(); + + @Mock + private Context mContext; + @Mock + private PackageManager mPackageManager; + @Mock + private PowerStatsCollector.ConsumedEnergyRetriever mConsumedEnergyRetriever; + private final PowerStatsUidResolver mPowerStatsUidResolver = new PowerStatsUidResolver(); + + private BluetoothActivityEnergyInfo mBluetoothActivityEnergyInfo; + private final SparseLongArray mUidScanTimes = new SparseLongArray(); + + private final BluetoothPowerStatsCollector.BluetoothStatsRetriever mBluetoothStatsRetriever = + new BluetoothPowerStatsCollector.BluetoothStatsRetriever() { + @Override + public void retrieveBluetoothScanTimes(Callback callback) { + for (int i = 0; i < mUidScanTimes.size(); i++) { + callback.onBluetoothScanTime(mUidScanTimes.keyAt(i), + mUidScanTimes.valueAt(i)); + } + } + + @Override + public boolean requestControllerActivityEnergyInfo(Executor executor, + BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback) { + callback.onBluetoothActivityEnergyInfoAvailable(mBluetoothActivityEnergyInfo); + return true; + } + }; + + private final BluetoothPowerStatsCollector.Injector mInjector = + new BluetoothPowerStatsCollector.Injector() { + @Override + public Handler getHandler() { + return mStatsRule.getHandler(); + } + + @Override + public Clock getClock() { + return mStatsRule.getMockClock(); + } + + @Override + public PowerStatsUidResolver getUidResolver() { + return mPowerStatsUidResolver; + } + + @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override + public PackageManager getPackageManager() { + return mPackageManager; + } + + @Override + public PowerStatsCollector.ConsumedEnergyRetriever getConsumedEnergyRetriever() { + return mConsumedEnergyRetriever; + } + + @Override + public IntSupplier getVoltageSupplier() { + return () -> VOLTAGE_MV; + } + + @Override + public BluetoothStatsRetriever getBluetoothStatsRetriever() { + return mBluetoothStatsRetriever; + } + }; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)).thenReturn(true); + } + + @Test + public void powerProfileModel_mostlyDataTransfer() { + // No power monitoring hardware + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH)) + .thenReturn(new int[0]); + + BluetoothPowerStatsProcessor processor = + new BluetoothPowerStatsProcessor(mStatsRule.getPowerProfile()); + + PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); + + BluetoothPowerStatsCollector collector = new BluetoothPowerStatsCollector(mInjector); + collector.setEnabled(true); + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1000, 600, 100, 200, + mockUidTraffic(APP_UID1, 100, 200), + mockUidTraffic(APP_UID2, 300, 400)); + + mUidScanTimes.put(APP_UID1, 100); + + // Establish a baseline + aggregatedStats.addPowerStats(collector.collectStats(), 0); + + // Turn the screen off after 2.5 seconds + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); + aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500); + aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, + 5000); + + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1100, 6600, 1100, 2200, + mockUidTraffic(APP_UID1, 1100, 2200), + mockUidTraffic(APP_UID2, 3300, 4400)); + + mUidScanTimes.clear(); + mUidScanTimes.put(APP_UID1, 200); + mUidScanTimes.put(APP_UID2, 300); + + mStatsRule.setTime(10_000, 10_000); + + aggregatedStats.addPowerStats(collector.collectStats(), 10_000); + + processor.finish(aggregatedStats); + + BluetoothPowerStatsLayout statsLayout = + new BluetoothPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor()); + + // RX power = 'rx-duration * PowerProfile[bluetooth.controller.rx]` + // RX power = 6000 * 50 = 300000 mA-ms = 0.083333 mAh + // TX power = 'tx-duration * PowerProfile[bluetooth.controller.tx]` + // TX power = 1000 * 100 = 100000 mA-ms = 0.02777 mAh + // Idle power = 'idle-duration * PowerProfile[bluetooth.controller.idle]` + // Idle power = 2000 * 10 = 20000 mA-ms = 0.00555 mAh + // Total power = RX + TX + Idle = 0.116666 + // Screen-on - 25% + // Screen-off - 75% + double expectedPower = 0.116666; + long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(expectedPower * 0.25); + + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(expectedPower * 0.75); + + // UID1 = + // (1000 / 4000) * 0.083333 // rx + // + (2000 / 6000) * 0.027777 // tx + // = 0.030092 mAh + double expectedPower1 = 0.030092; + long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.5); + + // UID2 = + // (3000 / 4000) * 0.083333 // rx + // + (4000 / 6000) * 0.027777 // tx + // = 0.08102 mAh + double expectedPower2 = 0.08102; + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower2 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower2 * 0.75); + } + + @Test + public void powerProfileModel_mostlyScan() { + // No power monitoring hardware + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH)) + .thenReturn(new int[0]); + + BluetoothPowerStatsProcessor processor = + new BluetoothPowerStatsProcessor(mStatsRule.getPowerProfile()); + + PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); + + BluetoothPowerStatsCollector collector = new BluetoothPowerStatsCollector(mInjector); + collector.setEnabled(true); + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1000, 600, 100, 200, + mockUidTraffic(APP_UID1, 100, 200), + mockUidTraffic(APP_UID2, 300, 400)); + + mUidScanTimes.put(APP_UID1, 100); + + // Establish a baseline + aggregatedStats.addPowerStats(collector.collectStats(), 0); + + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); + aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500); + aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, + 5000); + + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1100, 6600, 1100, 2200, + mockUidTraffic(APP_UID1, 1100, 2200), + mockUidTraffic(APP_UID2, 3300, 4400)); + + // Total scan time exceeding data transfer times + mUidScanTimes.clear(); + mUidScanTimes.put(APP_UID1, 3100); + mUidScanTimes.put(APP_UID2, 5000); + + mStatsRule.setTime(10_000, 10_000); + + aggregatedStats.addPowerStats(collector.collectStats(), 10_000); + + processor.finish(aggregatedStats); + + BluetoothPowerStatsLayout statsLayout = + new BluetoothPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor()); + + // RX power = 'rx-duration * PowerProfile[bluetooth.controller.rx]` + // RX power = 6000 * 50 = 300000 mA-ms = 0.083333 mAh + // TX power = 'tx-duration * PowerProfile[bluetooth.controller.tx]` + // TX power = 1000 * 100 = 100000 mA-ms = 0.02777 mAh + // Idle power = 'idle-duration * PowerProfile[bluetooth.controller.idle]` + // Idle power = 2000 * 10 = 20000 mA-ms = 0.00555 mAh + // Total power = RX + TX + Idle = 0.116666 + double expectedPower = 0.116666; + long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(expectedPower * 0.25); + + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(expectedPower * 0.75); + + // UID1 = + // (3000 / 8000) * 0.083333 // rx + // + (3000 / 8000) * 0.027777 // tx + // = 0.041666 mAh + double expectedPower1 = 0.041666; + long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.5); + + // UID2 = + // (5000 / 8000) * 0.083333 // rx + // + (5000 / 8000) * 0.027777 // tx + // = 0.069443 mAh + double expectedPower2 = 0.069443; + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower2 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower2 * 0.75); + } + + @Test + public void consumedEnergyModel() { + // No power monitoring hardware + when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.BLUETOOTH)) + .thenReturn(new int[]{BLUETOOTH_ENERGY_CONSUMER_ID}); + + BluetoothPowerStatsProcessor processor = + new BluetoothPowerStatsProcessor(mStatsRule.getPowerProfile()); + + PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); + + BluetoothPowerStatsCollector collector = new BluetoothPowerStatsCollector(mInjector); + collector.setEnabled(true); + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1000, 600, 100, 200, + mockUidTraffic(APP_UID1, 100, 200), + mockUidTraffic(APP_UID2, 300, 400)); + + mUidScanTimes.put(APP_UID1, 100); + + when(mConsumedEnergyRetriever.getConsumedEnergyUws( + new int[]{BLUETOOTH_ENERGY_CONSUMER_ID})).thenReturn(new long[]{0}); + + // Establish a baseline + aggregatedStats.addPowerStats(collector.collectStats(), 0); + + // Turn the screen off after 2.5 seconds + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_OTHER, 2500); + aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_BACKGROUND, 2500); + aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND_SERVICE, + 5000); + + mBluetoothActivityEnergyInfo = mockBluetoothActivityEnergyInfo(1100, 6600, 1100, 2200, + mockUidTraffic(APP_UID1, 1100, 2200), + mockUidTraffic(APP_UID2, 3300, 4400)); + + mUidScanTimes.clear(); + mUidScanTimes.put(APP_UID1, 200); + mUidScanTimes.put(APP_UID2, 300); + + mStatsRule.setTime(10_000, 10_000); + + // 10 mAh represented as microWattSeconds + long energyUws = 10 * 3600 * VOLTAGE_MV; + when(mConsumedEnergyRetriever.getConsumedEnergyUws( + new int[]{BLUETOOTH_ENERGY_CONSUMER_ID})).thenReturn(new long[]{energyUws}); + + aggregatedStats.addPowerStats(collector.collectStats(), 10_000); + + processor.finish(aggregatedStats); + + BluetoothPowerStatsLayout statsLayout = + new BluetoothPowerStatsLayout(aggregatedStats.getPowerStatsDescriptor()); + + // All estimates are computed as in the #powerProfileModel_mostlyDataTransfer test, + // except they are all scaled by the same ratio to ensure that the total estimated + // energy is equal to the measured energy + double expectedPower = 10; + long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(expectedPower * 0.25); + + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(expectedPower * 0.75); + + // UID1 + // 0.030092 // power profile model estimate + // 0.116666 // power profile model estimate for total power + // 10 // total consumed energy + // = 0.030092 * (10 / 0.116666) = 2.579365 + double expectedPower1 = 2.579365; + long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID1, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower1 * 0.5); + + // UID2 = + // 0.08102 // power profile model estimate + // 0.116666 // power profile model estimate for total power + // 10 // total consumed energy + // = 0.08102 * (10 / 0.116666) = 6.944444 + double expectedPower2 = 6.944444; + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower2 * 0.25); + + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(expectedPower2 * 0.75); + } + + private static PowerComponentAggregatedPowerStats createAggregatedPowerStats( + BluetoothPowerStatsProcessor processor) { + AggregatedPowerStatsConfig.PowerComponent config = + new AggregatedPowerStatsConfig.PowerComponent( + BatteryConsumer.POWER_COMPONENT_BLUETOOTH) + .trackDeviceStates(STATE_POWER, STATE_SCREEN) + .trackUidStates(STATE_POWER, STATE_SCREEN, STATE_PROCESS_STATE) + .setProcessor(processor); + + PowerComponentAggregatedPowerStats aggregatedStats = + new PowerComponentAggregatedPowerStats( + new AggregatedPowerStats(mock(AggregatedPowerStatsConfig.class)), config); + + aggregatedStats.setState(STATE_POWER, POWER_STATE_OTHER, 0); + aggregatedStats.setState(STATE_SCREEN, SCREEN_STATE_ON, 0); + aggregatedStats.setUidState(APP_UID1, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); + aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); + + return aggregatedStats; + } + + private int[] states(int... states) { + return states; + } + + private BluetoothActivityEnergyInfo mockBluetoothActivityEnergyInfo(long timestamp, + long rxTimeMs, long txTimeMs, long idleTimeMs, UidTraffic... uidTraffic) { + if (RavenwoodRule.isOnRavenwood()) { + BluetoothActivityEnergyInfo info = mock(BluetoothActivityEnergyInfo.class); + when(info.getControllerRxTimeMillis()).thenReturn(rxTimeMs); + when(info.getControllerTxTimeMillis()).thenReturn(txTimeMs); + when(info.getControllerIdleTimeMillis()).thenReturn(idleTimeMs); + when(info.getUidTraffic()).thenReturn(List.of(uidTraffic)); + return info; + } else { + final Parcel btActivityEnergyInfoParcel = Parcel.obtain(); + btActivityEnergyInfoParcel.writeLong(timestamp); + btActivityEnergyInfoParcel.writeInt( + BluetoothActivityEnergyInfo.BT_STACK_STATE_STATE_ACTIVE); + btActivityEnergyInfoParcel.writeLong(txTimeMs); + btActivityEnergyInfoParcel.writeLong(rxTimeMs); + btActivityEnergyInfoParcel.writeLong(idleTimeMs); + btActivityEnergyInfoParcel.writeLong(0L); + btActivityEnergyInfoParcel.writeTypedList(List.of(uidTraffic)); + btActivityEnergyInfoParcel.setDataPosition(0); + + BluetoothActivityEnergyInfo info = BluetoothActivityEnergyInfo.CREATOR + .createFromParcel(btActivityEnergyInfoParcel); + btActivityEnergyInfoParcel.recycle(); + return info; + } + } + + private UidTraffic mockUidTraffic(int uid, long rxBytes, long txBytes) { + if (RavenwoodRule.isOnRavenwood()) { + UidTraffic traffic = mock(UidTraffic.class); + when(traffic.getUid()).thenReturn(uid); + when(traffic.getRxBytes()).thenReturn(rxBytes); + when(traffic.getTxBytes()).thenReturn(txBytes); + return traffic; + } else { + final Parcel uidTrafficParcel = Parcel.obtain(); + uidTrafficParcel.writeInt(uid); + uidTrafficParcel.writeLong(rxBytes); + uidTrafficParcel.writeLong(txBytes); + uidTrafficParcel.setDataPosition(0); + + UidTraffic traffic = UidTraffic.CREATOR.createFromParcel(uidTrafficParcel); + uidTrafficParcel.recycle(); + return traffic; + } + } +} diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java index 4e3e80f8ff10..d1105a4a9077 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorTest.java @@ -32,6 +32,7 @@ import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.platform.test.ravenwood.RavenwoodRule; +import android.util.IndentingPrintWriter; import android.util.SparseArray; import androidx.test.filters.SmallTest; @@ -51,6 +52,7 @@ import org.mockito.MockitoAnnotations; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.StringWriter; import java.util.function.IntSupplier; @RunWith(AndroidJUnit4.class) @@ -127,6 +129,11 @@ public class CpuPowerStatsCollectorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public int getDefaultCpuPowerBrackets() { return mDefaultCpuPowerBrackets; } @@ -363,6 +370,36 @@ public class CpuPowerStatsCollectorTest { .isEqualTo(528); } + @Test + public void dump() { + mockCpuScalingPolicies(1); + mockPowerProfile(); + mockEnergyConsumers(); + + CpuPowerStatsCollector collector = createCollector(8, 0); + collector.collectStats(); // Establish baseline + + mockKernelCpuStats(new long[]{1111, 2222, 3333}, + new SparseArray<>() {{ + put(UID_1, new long[]{100, 200}); + put(UID_2, new long[]{100, 150}); + put(ISOLATED_UID, new long[]{200, 450}); + }}, 0, 1234); + + PowerStats powerStats = collector.collectStats(); + + StringWriter sw = new StringWriter(); + IndentingPrintWriter pw = new IndentingPrintWriter(sw); + powerStats.dump(pw); + pw.flush(); + String dump = sw.toString(); + + assertThat(dump).contains("duration=1234"); + assertThat(dump).contains("steps: [1111, 2222, 3333]"); + assertThat(dump).contains("UID 42: time: [100, 200]"); + assertThat(dump).contains("UID 99: time: [300, 600]"); + } + private void mockCpuScalingPolicies(int clusterCount) { SparseArray<int[]> cpus = new SparseArray<>(); SparseArray<int[]> freqs = new SparseArray<>(); @@ -386,8 +423,8 @@ public class CpuPowerStatsCollectorTest { private CpuPowerStatsCollector createCollector(int defaultCpuPowerBrackets, int defaultCpuPowerBracketsPerEnergyConsumer) { CpuPowerStatsCollector collector = new CpuPowerStatsCollector( - new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer), - 0); + new TestInjector(defaultCpuPowerBrackets, defaultCpuPowerBracketsPerEnergyConsumer) + ); collector.addConsumer(stats -> mCollectedStats = stats); collector.setEnabled(true); return collector; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java index 70c40f5052f0..644ae4717eb1 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerStatsCollectorValidationTest.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; +import android.os.UserHandle; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -46,7 +47,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; @@ -92,9 +95,10 @@ public class CpuPowerStatsCollectorValidationTest { long duration = 0; long[] stats = null; - String[] cpuStatsDump = dumpCpuStats(); + List<String> cpuStatsDump = dumpCpuStats(); Pattern durationPattern = Pattern.compile("duration=([0-9]*)"); - Pattern uidPattern = Pattern.compile("UID " + mTestPkgUid + ": \\[([0-9,\\s]*)]"); + Pattern uidPattern = Pattern.compile( + "UID " + UserHandle.formatUid(mTestPkgUid) + ": time: [\\[]?([0-9,\\s]*)[]]?"); for (String line : cpuStatsDump) { Matcher durationMatcher = durationPattern.matcher(line); if (durationMatcher.find()) { @@ -119,15 +123,23 @@ public class CpuPowerStatsCollectorValidationTest { assertThat(total).isAtLeast((long) (WORK_DURATION_MS * 0.8)); } - private String[] dumpCpuStats() throws Exception { + private List<String> dumpCpuStats() throws Exception { + ArrayList<String> cpuStats = new ArrayList<>(); String dump = executeCmdSilent("dumpsys batterystats --sample"); String[] lines = dump.split("\n"); + boolean inCpuSection = false; for (int i = 0; i < lines.length; i++) { - if (lines[i].startsWith("CpuPowerStatsCollector")) { - return Arrays.copyOfRange(lines, i + 1, lines.length); + if (!inCpuSection) { + if (lines[i].startsWith("CpuPowerStatsCollector")) { + inCpuSection = true; + } + } else if (lines[i].startsWith(" ")) { + cpuStats.add(lines[i]); + } else { + break; } } - return new String[0]; + return cpuStats; } private void doSomeWork() throws Exception { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java index f93c4da3d8d0..0275319a40e2 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java @@ -123,6 +123,11 @@ public class MobileRadioPowerStatsCollectorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -337,16 +342,18 @@ public class MobileRadioPowerStatsCollectorTest { pw.flush(); String dump = sw.toString(); assertThat(dump).contains("duration=100"); + assertThat(dump).contains("sleep: 200 idle: 300 scan: 60000 call: 40000 energy: " + + ((64321 - 10000) * 1000 / 3500)); + assertThat(dump).contains("(LTE) rx: 7000 tx: [8000, 9000, 1000, 2000, 3000]"); + assertThat(dump).contains("(NR MMWAVE) rx: 6000 tx: [1000, 2000, 3000, 4000, 5000]"); + assertThat(dump).contains( + "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000"); assertThat(dump).contains( - "stats=[200, 300, 60000, 40000, " + ((64321 - 10000) * 1000 / 3500) + ", 0, 0, 0]"); - assertThat(dump).contains("state LTE: [7000, 8000, 9000, 1000, 2000, 3000]"); - assertThat(dump).contains("state NR MMWAVE: [6000, 1000, 2000, 3000, 4000, 5000]"); - assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 0]"); - assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 0]"); + "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000"); } private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable { - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); collector.setEnabled(true); when(mConsumedEnergyRetriever.getEnergyConsumerIds( diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java index 4ac7ad8d07ff..29ef3b6551a6 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java @@ -114,6 +114,11 @@ public class MobileRadioPowerStatsProcessorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -186,7 +191,7 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty ModemActivityInfo. @@ -305,7 +310,100 @@ public class MobileRadioPowerStatsProcessorTest { } @Test - public void measuredEnergyModel() { + public void energyConsumerModel() { + PowerComponentAggregatedPowerStats aggregatedStats = + prepareAggregatedStats_energyConsumerModel(); + + MobileRadioPowerStatsLayout statsLayout = + new MobileRadioPowerStatsLayout( + aggregatedStats.getPowerStatsDescriptor()); + + // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh + double totalPower = 0; + long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.671837); + totalPower += statsLayout.getDevicePowerEstimate(deviceStats); + assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.022494); + totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); + + aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); + assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) + .isWithin(PRECISION).of(2.01596); + totalPower += statsLayout.getDevicePowerEstimate(deviceStats); + assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) + .isWithin(PRECISION).of(0.067484); + totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); + + // These estimates are supposed to add up to the measured energy, 2.77778 mAh + assertThat(totalPower).isWithin(PRECISION).of(2.77778); + + double uidPower1 = 0; + long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.396473); + uidPower1 += statsLayout.getUidPowerEstimate(uidStats); + + double uidPower2 = 0; + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.066078); + uidPower2 += statsLayout.getUidPowerEstimate(uidStats); + + aggregatedStats.getUidStats(uidStats, APP_UID2, + states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); + assertThat(statsLayout.getUidPowerEstimate(uidStats)) + .isWithin(PRECISION).of(0.198236); + uidPower2 += statsLayout.getUidPowerEstimate(uidStats); + + // Total power attributed to apps is significantly less than the grand total, + // because we only attribute TX/RX to apps but not maintaining a connection with the cell. + assertThat(uidPower1 + uidPower2) + .isWithin(PRECISION).of(1.057259); + + // 3/4 of total packets were sent by APP_UID so 75% of total RX/TX power is attributed to it + assertThat(uidPower1 / (uidPower1 + uidPower2)) + .isWithin(PRECISION).of(0.75); + } + + @Test + public void test_toString() { + PowerComponentAggregatedPowerStats stats = prepareAggregatedStats_energyConsumerModel(); + String string = stats.toString(); + assertThat(string).contains("(pwr-other scr-on)" + + " sleep: 500 idle: 750 scan: 1388 call: 50 energy: 2500000 power: 0.672"); + assertThat(string).contains("(pwr-other scr-other)" + + " sleep: 1500 idle: 2250 scan: 4166 call: 150 energy: 7500000 power: 2.02"); + assertThat(string).contains("(pwr-other scr-on other)" + + " rx: 150 tx: [25, 50, 75, 100, 125]"); + assertThat(string).contains("(pwr-other scr-other other)" + + " rx: 450 tx: [75, 150, 225, 300, 375]"); + assertThat(string).contains("(pwr-other scr-on fg)" + + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198"); + assertThat(string).contains("(pwr-other scr-other bg)" + + " rx-pkts: 375 rx-B: 2500 tx-pkts: 75 tx-B: 5000 power: 0.198"); + assertThat(string).contains("(pwr-other scr-other fgs)" + + " rx-pkts: 750 rx-B: 5000 tx-pkts: 150 tx-B: 10000 power: 0.396"); + } + + private PowerComponentAggregatedPowerStats prepareAggregatedStats_energyConsumerModel() { // PowerStats hardware is available when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.MOBILE_RADIO)) .thenReturn(new int[] {MOBILE_RADIO_ENERGY_CONSUMER_ID}); @@ -332,7 +430,7 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0); aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty ModemActivityInfo. @@ -378,74 +476,7 @@ public class MobileRadioPowerStatsProcessorTest { aggregatedStats.addPowerStats(powerStats, 10_000); processor.finish(aggregatedStats); - - MobileRadioPowerStatsLayout statsLayout = - new MobileRadioPowerStatsLayout( - aggregatedStats.getPowerStatsDescriptor()); - - // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh - double totalPower = 0; - long[] deviceStats = new long[aggregatedStats.getPowerStatsDescriptor().statsArrayLength]; - aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_ON)); - assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) - .isWithin(PRECISION).of(0.671837); - totalPower += statsLayout.getDevicePowerEstimate(deviceStats); - assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) - .isWithin(PRECISION).of(0.022494); - totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); - - aggregatedStats.getDeviceStats(deviceStats, states(POWER_STATE_OTHER, SCREEN_STATE_OTHER)); - assertThat(statsLayout.getDevicePowerEstimate(deviceStats)) - .isWithin(PRECISION).of(2.01596); - totalPower += statsLayout.getDevicePowerEstimate(deviceStats); - assertThat(statsLayout.getDeviceCallPowerEstimate(deviceStats)) - .isWithin(PRECISION).of(0.067484); - totalPower += statsLayout.getDeviceCallPowerEstimate(deviceStats); - - // These estimates are supposed to add up to the measured energy, 2.77778 mAh - assertThat(totalPower).isWithin(PRECISION).of(2.77778); - - double uidPower1 = 0; - long[] uidStats = new long[aggregatedStats.getPowerStatsDescriptor().uidStatsArrayLength]; - aggregatedStats.getUidStats(uidStats, APP_UID, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_FOREGROUND)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.198236); - uidPower1 += statsLayout.getUidPowerEstimate(uidStats); - - aggregatedStats.getUidStats(uidStats, APP_UID, - states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_BACKGROUND)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.198236); - uidPower1 += statsLayout.getUidPowerEstimate(uidStats); - - aggregatedStats.getUidStats(uidStats, APP_UID, - states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_FOREGROUND_SERVICE)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.396473); - uidPower1 += statsLayout.getUidPowerEstimate(uidStats); - - double uidPower2 = 0; - aggregatedStats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_ON, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.066078); - uidPower2 += statsLayout.getUidPowerEstimate(uidStats); - - aggregatedStats.getUidStats(uidStats, APP_UID2, - states(POWER_STATE_OTHER, SCREEN_STATE_OTHER, PROCESS_STATE_CACHED)); - assertThat(statsLayout.getUidPowerEstimate(uidStats)) - .isWithin(PRECISION).of(0.198236); - uidPower2 += statsLayout.getUidPowerEstimate(uidStats); - - // Total power attributed to apps is significantly less than the grand total, - // because we only attribute TX/RX to apps but not maintaining a connection with the cell. - assertThat(uidPower1 + uidPower2) - .isWithin(PRECISION).of(1.057259); - - // 3/4 of total packets were sent by APP_UID so 75% of total RX/TX power is attributed to it - assertThat(uidPower1 / (uidPower1 + uidPower2)) - .isWithin(PRECISION).of(0.75); + return aggregatedStats; } private int[] states(int... states) { diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index 1d48975c086e..2c03f9d1a9aa 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -72,6 +72,11 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { this(DEFAULT_CONFIG, clock, historyDirectory, handler, new PowerStatsUidResolver()); } + MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory) { + this(config, clock, historyDirectory, new Handler(Looper.getMainLooper()), + new PowerStatsUidResolver()); + } + MockBatteryStatsImpl(BatteryStatsConfig config, Clock clock, File historyDirectory, Handler handler, PowerStatsUidResolver powerStatsUidResolver) { super(config, clock, new MonotonicClock(0, clock), historyDirectory, handler, @@ -137,13 +142,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { return MOBILE_RADIO_POWER_STATE_UPDATE_FREQ_MS; } - public MockBatteryStatsImpl setBatteryStatsConfig(BatteryStatsConfig config) { - synchronized (this) { - mBatteryStatsConfig = config; - } - return this; - } - public MockBatteryStatsImpl setNetworkStats(NetworkStats networkStats) { mNetworkStats = networkStats; return this; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java index 1b045c532759..ae258cd3c234 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java @@ -29,8 +29,6 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.PrintWriter; -import java.io.StringWriter; import java.util.Arrays; @RunWith(AndroidJUnit4.class) @@ -165,7 +163,7 @@ public class MultiStateStatsTest { } @Test - public void dump() { + public void test_toString() { MultiStateStats.Factory factory = makeFactory(true, true, false); MultiStateStats multiStateStats = factory.create(); multiStateStats.setState(0 /* batteryState */, 0 /* off */, 1000); @@ -175,13 +173,10 @@ public class MultiStateStatsTest { multiStateStats.setState(1 /* procState */, BatteryConsumer.PROCESS_STATE_BACKGROUND, 3000); multiStateStats.increment(new long[]{100, 200}, 5000); - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw, true); - multiStateStats.dump(pw, Arrays::toString); - assertThat(sw.toString()).isEqualTo( - "plugged-in fg [25, 50]\n" - + "on-battery fg [25, 50]\n" - + "on-battery bg [50, 100]\n" + assertThat(multiStateStats.toString()).isEqualTo( + "(plugged-in fg) [25, 50]\n" + + "(on-battery fg) [25, 50]\n" + + "(on-battery bg) [50, 100]" ); } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java index dadcf3f3871e..69d655bc35c0 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java @@ -98,6 +98,11 @@ public class PhoneCallPowerStatsProcessorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -175,7 +180,7 @@ public class PhoneCallPowerStatsProcessorTest { aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0); aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0); - MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector, 0); + MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty ModemActivityInfo. diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java index 8b1d423abd21..a280cfe176a3 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java @@ -138,6 +138,11 @@ public class WifiPowerStatsCollectorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -304,16 +309,20 @@ public class WifiPowerStatsCollectorTest { String dump = sw.toString(); assertThat(dump).contains("duration=7500"); assertThat(dump).contains( - "stats=[6000, 1000, 300, 200, 634, 945, " + ((64321 - 10000) * 1000 / 3500) - + ", 0, 0]"); - assertThat(dump).contains("UID 24: [6000, 3000, 60, 30, 400, 600, 0]"); - assertThat(dump).contains("UID 42: [1000, 2000, 100, 200, 234, 345, 0]"); + "rx: 6000 tx: 1000 idle: 300 scan: 200 basic-scan: 634 batched-scan: 945" + + " energy: " + ((64321 - 10000) * 1000 / 3500)); + assertThat(dump).contains( + "UID 24: rx-pkts: 60 rx-B: 6000 tx-pkts: 30 tx-B: 3000" + + " scan: 400 batched-scan: 600"); + assertThat(dump).contains( + "UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000" + + " scan: 234 batched-scan: 345"); } private PowerStats collectPowerStats(boolean hasPowerReporting) { when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(hasPowerReporting); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); collector.setEnabled(true); when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI)) diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java index 257a1a67f7b0..3ceaf357150e 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java @@ -142,6 +142,11 @@ public class WifiPowerStatsProcessorTest { } @Override + public long getPowerStatsCollectionThrottlePeriod(String powerComponentName) { + return 0; + } + + @Override public PackageManager getPackageManager() { return mPackageManager; } @@ -195,7 +200,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty WifiActivityEnergyInfo. @@ -307,7 +312,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); collector.setEnabled(true); // Initial empty WifiActivityEnergyInfo. @@ -420,7 +425,7 @@ public class WifiPowerStatsProcessorTest { PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor); - WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, 0); + WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector); collector.setEnabled(true); // Establish a baseline diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml index 27c522d68119..b56af87ee020 100644 --- a/services/tests/servicestests/AndroidTest.xml +++ b/services/tests/servicestests/AndroidTest.xml @@ -25,6 +25,13 @@ value="/data/local/tmp/cts/content/broken_shortcut.xml" /> </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.DeviceSetup"> + <option name="force-skip-system-props" value="true" /> + <option name="set-global-setting" key="verifier_engprod" value="1" /> + <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" /> + <option name="restore-settings" value="true" /> + </target_preparer> + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="install-arg" value="-t" /> 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/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 16d05b157727..6e6d5a870031 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -89,6 +89,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.test.FakePermissionEnforcer; import android.util.Pair; import android.view.Display; import android.view.KeyEvent; @@ -181,6 +182,7 @@ public class AbstractAccessibilityServiceConnectionTest { @Mock private FingerprintGestureDispatcher mMockFingerprintGestureDispatcher; @Mock private MagnificationProcessor mMockMagnificationProcessor; @Mock private RemoteCallback.OnResultListener mMockListener; + FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer(); @Before public void setup() { @@ -198,6 +200,8 @@ public class AbstractAccessibilityServiceConnectionTest { PowerManager powerManager = new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler); when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(powerManager); + when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE)) + .thenReturn(mFakePermissionEnforcer); when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockPackageManager.hasSystemFeature(FEATURE_FINGERPRINT)).thenReturn(true); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java index cb4fc753aa4d..6152326bb6d4 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java @@ -30,7 +30,9 @@ import static com.android.window.flags.Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULL import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -42,6 +44,7 @@ import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -102,6 +105,7 @@ import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutT import com.android.internal.accessibility.util.AccessibilityUtils; import com.android.internal.accessibility.util.ShortcutUtils; import com.android.internal.compat.IPlatformCompat; +import com.android.internal.content.PackageMonitor; import com.android.server.LocalServices; import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener; import com.android.server.accessibility.magnification.FullScreenMagnificationController; @@ -910,6 +914,38 @@ public class AccessibilityManagerServiceTest { } @Test + public void onPackageChanged_disableComponent_updateInstalledServices() { + // Sets up two accessibility services as installed services + setupShortcutTargetServices(); + assertThat(mA11yms.getCurrentUserState().mInstalledServices).hasSize(2); + AccessibilityServiceInfo installedService1 = + mA11yms.getCurrentUserState().mInstalledServices.getFirst(); + ResolveInfo resolveInfo1 = installedService1.getResolveInfo(); + AccessibilityServiceInfo installedService2 = + mA11yms.getCurrentUserState().mInstalledServices.getLast(); + + // Disables `installedService2` + when(mMockPackageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())) + .thenReturn(List.of(resolveInfo1)); + when(mMockSecurityPolicy.canRegisterService(any())).thenReturn(true); + final Intent packageIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED); + packageIntent.setData( + Uri.parse("package:" + installedService2.getResolveInfo().serviceInfo.packageName)); + packageIntent.putExtra(Intent.EXTRA_UID, UserHandle.myUserId()); + packageIntent.putExtra(Intent.EXTRA_USER_HANDLE, mA11yms.getCurrentUserIdLocked()); + packageIntent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, + new String[]{ + installedService2.getComponentName().flattenToString()}); + mA11yms.getPackageMonitor().doHandlePackageEvent(packageIntent); + + assertThat(mA11yms.getCurrentUserState().mInstalledServices).hasSize(1); + ComponentName installedService = + mA11yms.getCurrentUserState().mInstalledServices.getFirst().getComponentName(); + assertThat(installedService) + .isEqualTo(installedService1.getComponentName()); + } + + @Test public void testSwitchUserScanPackages_scansWithoutHoldingLock() { setupAccessibilityServiceConnection(0); final AtomicReference<Set<Boolean>> lockState = collectLockStateWhilePackageScanning(); @@ -1620,6 +1656,67 @@ public class AccessibilityManagerServiceTest { .containsExactlyElementsIn(Set.of(daltonizerTile)); } + @Test + @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX) + public void onHandleForceStop_dontDoIt_packageEnabled_returnsTrue() { + setupShortcutTargetServices(); + AccessibilityUserState userState = mA11yms.getCurrentUserState(); + userState.mEnabledServices.addAll( + userState.mInstalledServices.stream().map( + (AccessibilityServiceInfo::getComponentName)).toList()); + String[] packages = userState.mEnabledServices.stream().map( + ComponentName::getPackageName).toList().toArray(new String[0]); + + PackageMonitor monitor = spy(mA11yms.getPackageMonitor()); + when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM); + mA11yms.setPackageMonitor(monitor); + + assertTrue(mA11yms.getPackageMonitor().onHandleForceStop( + new Intent(), + packages, + UserHandle.USER_SYSTEM, + false + )); + } + + @Test + @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX) + public void onHandleForceStop_doIt_packageEnabled_returnsFalse() { + setupShortcutTargetServices(); + AccessibilityUserState userState = mA11yms.getCurrentUserState(); + userState.mEnabledServices.addAll( + userState.mInstalledServices.stream().map( + (AccessibilityServiceInfo::getComponentName)).toList()); + String[] packages = userState.mEnabledServices.stream().map( + ComponentName::getPackageName).toList().toArray(new String[0]); + + PackageMonitor monitor = spy(mA11yms.getPackageMonitor()); + when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM); + mA11yms.setPackageMonitor(monitor); + + assertFalse(mA11yms.getPackageMonitor().onHandleForceStop( + new Intent(), + packages, + UserHandle.USER_SYSTEM, + true + )); + } + + @Test + @EnableFlags(Flags.FLAG_MANAGER_PACKAGE_MONITOR_LOGIC_FIX) + public void onHandleForceStop_dontDoIt_packageNotEnabled_returnsFalse() { + PackageMonitor monitor = spy(mA11yms.getPackageMonitor()); + when(monitor.getChangingUserId()).thenReturn(UserHandle.USER_SYSTEM); + mA11yms.setPackageMonitor(monitor); + + assertFalse(mA11yms.getPackageMonitor().onHandleForceStop( + new Intent(), + new String[]{ "FOO", "BAR"}, + UserHandle.USER_SYSTEM, + false + )); + } + private static AccessibilityServiceInfo mockAccessibilityServiceInfo( ComponentName componentName) { return mockAccessibilityServiceInfo( @@ -1630,7 +1727,7 @@ public class AccessibilityManagerServiceTest { ComponentName componentName, boolean isSystemApp, boolean isAlwaysOnService) { AccessibilityServiceInfo accessibilityServiceInfo = - Mockito.spy(new AccessibilityServiceInfo()); + spy(new AccessibilityServiceInfo()); accessibilityServiceInfo.setComponentName(componentName); ResolveInfo mockResolveInfo = Mockito.mock(ResolveInfo.class); when(accessibilityServiceInfo.getResolveInfo()).thenReturn(mockResolveInfo); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index cda8b01afceb..c4946f0b221e 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java @@ -26,7 +26,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -56,6 +55,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; +import android.os.test.FakePermissionEnforcer; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -82,7 +82,6 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; - /** * Tests for AccessibilityServiceConnection */ @@ -130,6 +129,7 @@ public class AccessibilityServiceConnectionTest { IBrailleDisplayController mMockBrailleDisplayController; @Mock MotionEventInjector mMockMotionEventInjector; + FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer(); MessageCapturingHandler mHandler = new MessageCapturingHandler(null); @@ -151,12 +151,14 @@ public class AccessibilityServiceConnectionTest { when(mMockA11yTrace.isA11yTracingEnabled()).thenReturn(false); when(mMockContext.getSystemService(Context.DISPLAY_SERVICE)) .thenReturn(new DisplayManager(mMockContext)); + when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE)) + .thenReturn(mFakePermissionEnforcer); mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext, - COMPONENT_NAME, mServiceInfo, SERVICE_ID, mHandler, new Object(), - mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace, - mMockWindowManagerInternal, mMockSystemActionPerformer, - mMockA11yWindowManager, mMockActivityTaskManagerInternal); + COMPONENT_NAME, mServiceInfo, SERVICE_ID, mHandler, new Object(), + mMockSecurityPolicy, mMockSystemSupport, mMockA11yTrace, + mMockWindowManagerInternal, mMockSystemActionPerformer, + mMockA11yWindowManager, mMockActivityTaskManagerInternal); when(mMockSecurityPolicy.canPerformGestures(mConnection)).thenReturn(true); when(mMockSecurityPolicy.checkAccessibilityAccess(mConnection)).thenReturn(true); } @@ -317,6 +319,8 @@ public class AccessibilityServiceConnectionTest { @Test @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID) public void connectBluetoothBrailleDisplay() throws Exception { + mFakePermissionEnforcer.grant(Manifest.permission.BLUETOOTH_CONNECT); + mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY); final String macAddress = "00:11:22:33:AA:BB"; final byte[] descriptor = {0x05, 0x41}; Bundle bd = new Bundle(); @@ -338,9 +342,6 @@ public class AccessibilityServiceConnectionTest { @Test @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID) public void connectBluetoothBrailleDisplay_throwsForMissingBluetoothConnectPermission() { - doThrow(SecurityException.class).when(mMockContext) - .enforceCallingPermission(eq(Manifest.permission.BLUETOOTH_CONNECT), any()); - assertThrows(SecurityException.class, () -> mConnection.connectBluetoothBrailleDisplay("unused", mMockBrailleDisplayController)); @@ -349,6 +350,7 @@ public class AccessibilityServiceConnectionTest { @Test @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID) public void connectBluetoothBrailleDisplay_throwsForNullMacAddress() { + mFakePermissionEnforcer.grant(Manifest.permission.BLUETOOTH_CONNECT); assertThrows(NullPointerException.class, () -> mConnection.connectBluetoothBrailleDisplay(null, mMockBrailleDisplayController)); @@ -357,6 +359,7 @@ public class AccessibilityServiceConnectionTest { @Test @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID) public void connectBluetoothBrailleDisplay_throwsForMisformattedMacAddress() { + mFakePermissionEnforcer.grant(Manifest.permission.BLUETOOTH_CONNECT); assertThrows(IllegalArgumentException.class, () -> mConnection.connectBluetoothBrailleDisplay("12:34", mMockBrailleDisplayController)); @@ -365,6 +368,7 @@ public class AccessibilityServiceConnectionTest { @Test @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_BRAILLE_DISPLAY_HID) public void connectUsbBrailleDisplay() throws Exception { + mFakePermissionEnforcer.grant(Manifest.permission.MANAGE_ACCESSIBILITY); final String serialNumber = "myUsbDevice"; final byte[] descriptor = {0x05, 0x41}; Bundle bd = new Bundle(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java index 3d0db71ba17f..c0e5f760d05c 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyAccessibilityServiceConnectionTest.java @@ -38,6 +38,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.os.Handler; +import android.os.test.FakePermissionEnforcer; import android.view.accessibility.AccessibilityEvent; import com.android.server.wm.WindowManagerInternal; @@ -79,6 +80,7 @@ public class ProxyAccessibilityServiceConnectionTest { AccessibilityTrace mMockA11yTrace; @Mock WindowManagerInternal mMockWindowManagerInternal; + FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer(); ProxyAccessibilityServiceConnection mProxyConnection; AccessibilityServiceInfo mAccessibilityServiceInfo; private int mFocusStrokeWidthDefaultValue; @@ -90,6 +92,8 @@ public class ProxyAccessibilityServiceConnectionTest { MockitoAnnotations.initMocks(this); when(mMockContext.getResources()).thenReturn(resources); when(mMockSecurityPolicy.checkAccessibilityAccess(any())).thenReturn(true); + when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE)) + .thenReturn(mFakePermissionEnforcer); mAccessibilityServiceInfo = new AccessibilityServiceInfo(); mProxyConnection = new ProxyAccessibilityServiceConnection(mMockContext, COMPONENT_NAME, diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java index f1b356a2a0dc..52b33db556e6 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java @@ -50,6 +50,7 @@ import android.graphics.Region; import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.test.FakePermissionEnforcer; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -113,6 +114,7 @@ public class ProxyManagerTest { @Mock private IBinder mMockServiceAsBinder; @Mock private VirtualDeviceManagerInternal mMockVirtualDeviceManagerInternal; @Mock private IVirtualDeviceManager mMockIVirtualDeviceManager; + FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer(); private int mFocusStrokeWidthDefaultValue; private int mFocusColorDefaultValue; @@ -132,6 +134,8 @@ public class ProxyManagerTest { when(mMockContext.getMainExecutor()) .thenReturn(InstrumentationRegistry.getTargetContext().getMainExecutor()); + when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE)) + .thenReturn(mFakePermissionEnforcer); when(mMockVirtualDeviceManagerInternal.getDeviceIdsForUid(anyInt())).thenReturn( new ArraySet(Set.of(DEVICE_ID))); LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index 95a1f5a8a52f..e24592e556ea 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java @@ -39,6 +39,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.hardware.display.DisplayManager; import android.os.IBinder; +import android.os.test.FakePermissionEnforcer; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.view.WindowManager; @@ -80,12 +81,15 @@ public class UiAutomationManagerTest { @Mock IBinder mMockOwner; @Mock IAccessibilityServiceClient mMockAccessibilityServiceClient; @Mock IBinder mMockServiceAsBinder; + FakePermissionEnforcer mFakePermissionEnforcer = new FakePermissionEnforcer(); @Before public void setup() { MockitoAnnotations.initMocks(this); when(mMockSystemSupport.getKeyEventDispatcher()).thenReturn(mock(KeyEventDispatcher.class)); + when(mMockContext.getSystemService(Context.PERMISSION_ENFORCER_SERVICE)) + .thenReturn(mFakePermissionEnforcer); when(mMockServiceInfo.getResolveInfo()).thenReturn(mMockResolveInfo); mMockResolveInfo.serviceInfo = mock(ServiceInfo.class); 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/autofill/SaveEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java new file mode 100644 index 000000000000..9e5207817261 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/autofill/SaveEventLoggerTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.autofill; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; + +@RunWith(JUnit4.class) +public class SaveEventLoggerTest { + + @Test + public void testTimestampsInitialized() { + SaveEventLogger mLogger = spy(SaveEventLogger.forSessionId(1, 1)); + + mLogger.maybeSetLatencySaveUiDisplayMillis(); + mLogger.maybeSetLatencySaveRequestMillis(); + mLogger.maybeSetLatencySaveFinishMillis(); + + ArgumentCaptor<Long> latencySaveUiDisplayMillis = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor<Long> latencySaveRequestMillis = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor<Long> latencySaveFinishMillis = ArgumentCaptor.forClass(Long.class); + + verify(mLogger, times(1)) + .maybeSetLatencySaveUiDisplayMillis(latencySaveUiDisplayMillis.capture()); + verify(mLogger, times(1)) + .maybeSetLatencySaveRequestMillis(latencySaveRequestMillis.capture()); + verify(mLogger, times(1)) + .maybeSetLatencySaveFinishMillis(latencySaveFinishMillis.capture()); + + assertThat(latencySaveUiDisplayMillis.getValue()) + .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP); + assertThat(latencySaveRequestMillis.getValue()) + .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP); + assertThat(latencySaveFinishMillis.getValue()) + .isNotEqualTo(SaveEventLogger.UNINITIATED_TIMESTAMP); + } +} 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 new file mode 100644 index 000000000000..3698d6fb6b76 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricDanglingReceiverTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.biometrics; + +import static com.android.server.biometrics.sensors.BiometricNotificationUtils.NOTIFICATION_ID; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +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; +import android.provider.Settings; +import android.testing.TestableContext; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BiometricDanglingReceiverTest { + @Rule + public MockitoRule mockitoRule = MockitoJUnit.rule(); + + private BiometricDanglingReceiver mBiometricDanglingReceiver; + + @Rule + public final TestableContext mContext = spy(new TestableContext( + InstrumentationRegistry.getInstrumentation().getTargetContext(), null)); + + @Mock + NotificationManager mNotificationManager; + + @Mock + Intent mIntent; + + @Captor + private ArgumentCaptor<Intent> mArgumentCaptor; + + @Before + public void setUp() { + mContext.addMockSystemService(NotificationManager.class, mNotificationManager); + } + + @Test + public void testFingerprintRegisterReceiver() { + initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT); + 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(), + eq(Context.RECEIVER_NOT_EXPORTED)); + } + + @Test + public void testOnReceive_fingerprintReEnrollLaunch() { + initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FINGERPRINT); + when(mIntent.getAction()).thenReturn( + BiometricDanglingReceiver.ACTION_FINGERPRINT_RE_ENROLL_LAUNCH); + + mBiometricDanglingReceiver.onReceive(mContext, mIntent); + + // Verify fingerprint enroll process is launched. + verify(mContext).startActivity(mArgumentCaptor.capture()); + assertThat(mArgumentCaptor.getValue().getAction()) + .isEqualTo(Settings.ACTION_FINGERPRINT_ENROLL); + + // Verify notification is canceled + verify(mNotificationManager).cancelAsUser("FingerprintReEnroll", NOTIFICATION_ID, + UserHandle.CURRENT); + + // Verify receiver is unregistered after receiving the broadcast + verify(mContext).unregisterReceiver(mBiometricDanglingReceiver); + } + + @Test + public void testOnReceive_faceReEnrollLaunch() { + initBroadcastReceiver(BiometricsProtoEnums.MODALITY_FACE); + when(mIntent.getAction()).thenReturn( + BiometricDanglingReceiver.ACTION_FACE_RE_ENROLL_LAUNCH); + + mBiometricDanglingReceiver.onReceive(mContext, mIntent); + + // Verify face enroll process is launched. + verify(mContext).startActivity(mArgumentCaptor.capture()); + assertThat(mArgumentCaptor.getValue().getAction()) + .isEqualTo(BiometricDanglingReceiver.FACE_SETTINGS_ACTION); + + // Verify notification is canceled + verify(mNotificationManager).cancelAsUser("FaceReEnroll", NOTIFICATION_ID, + UserHandle.CURRENT); + + // Verify receiver is unregistered after receiving the broadcast. + verify(mContext).unregisterReceiver(mBiometricDanglingReceiver); + } + + private void initBroadcastReceiver(int modality) { + mBiometricDanglingReceiver = new BiometricDanglingReceiver(mContext, modality); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index fc573d243129..37895315557a 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -1228,6 +1228,11 @@ public class BiometricSchedulerTest { Slog.d(TAG, "TestInternalEnumerateClient#startHalOperation"); onEnumerationResult(TEST_FINGERPRINT, 0 /* remaining */); } + + @Override + protected int getModality() { + return BiometricsProtoEnums.MODALITY_FINGERPRINT; + } } private static class TestRemovalClient extends RemovalClient<Fingerprint, Object> { diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java index 9845b58f79bf..d8bdd50d8c08 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceInternalEnumerateClientTest.java @@ -20,8 +20,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -79,15 +81,21 @@ public class FaceInternalEnumerateClientTest { private final int mBiometricId = 1; private final Face mFace = new Face("face", mBiometricId, 1 /* deviceId */); private FaceInternalEnumerateClient mClient; + private boolean mNotificationSent; @Before public void setUp() { when(mAidlSession.getSession()).thenReturn(mSession); - final List<Face> enrolled = new ArrayList<>(); enrolled.add(mFace); - mClient = new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID, - TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext); + mClient = spy(new FaceInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID, + TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, mBiometricContext)); + + mNotificationSent = false; + doAnswer(invocation -> { + mNotificationSent = true; + return null; + }).when(mClient).sendDanglingNotification(anyList()); } @Test @@ -101,6 +109,7 @@ public class FaceInternalEnumerateClientTest { verify(mSession).enumerateEnrollments(); assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + assertThat(mNotificationSent).isFalse(); verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); verify(mCallback).onClientFinished(mClient, true); } @@ -116,6 +125,7 @@ public class FaceInternalEnumerateClientTest { verify(mSession).enumerateEnrollments(); assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + assertThat(mNotificationSent).isFalse(); verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); verify(mCallback, never()).onClientFinished(mClient, true); } @@ -131,6 +141,7 @@ public class FaceInternalEnumerateClientTest { verify(mSession).enumerateEnrollments(); assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + assertThat(mNotificationSent).isTrue(); verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); verify(mCallback).onClientFinished(mClient, true); } @@ -147,6 +158,7 @@ public class FaceInternalEnumerateClientTest { verify(mSession).enumerateEnrollments(); assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + assertThat(mNotificationSent).isFalse(); verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); verify(mCallback, never()).onClientFinished(mClient, true); } @@ -164,6 +176,7 @@ public class FaceInternalEnumerateClientTest { verify(mSession).enumerateEnrollments(); assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(1); + assertThat(mNotificationSent).isTrue(); verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, mBiometricId); verify(mCallback).onClientFinished(mClient, true); } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java index b5df8362b09f..fab120016434 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalEnumerateClientTest.java @@ -20,8 +20,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -80,15 +82,23 @@ public class FingerprintInternalEnumerateClientTest { private FingerprintInternalEnumerateClient mClient; + private boolean mNotificationSent; + @Before public void setUp() { when(mAidlSession.getSession()).thenReturn(mSession); List<Fingerprint> enrolled = new ArrayList<>(); enrolled.add(new Fingerprint("one", 1, 1)); - mClient = new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken, + mClient = spy(new FingerprintInternalEnumerateClient(mContext, () -> mAidlSession, mToken, USER_ID, TAG, enrolled, mBiometricUtils, SENSOR_ID, mBiometricLogger, - mBiometricContext); + mBiometricContext)); + + mNotificationSent = false; + doAnswer(invocation -> { + mNotificationSent = true; + return null; + }).when(mClient).sendDanglingNotification(anyList()); } @Test @@ -104,6 +114,7 @@ public class FingerprintInternalEnumerateClientTest { assertThat(mClient.getUnknownHALTemplates().stream() .flatMap(x -> Stream.of(x.getBiometricId())) .collect(Collectors.toList())).containsExactly(2, 3); + assertThat(mNotificationSent).isTrue(); verify(mBiometricUtils).removeBiometricForUser(mContext, USER_ID, 1); verify(mCallback).onClientFinished(mClient, true); } @@ -118,6 +129,7 @@ public class FingerprintInternalEnumerateClientTest { verify(mSession).enumerateEnrollments(); assertThat(mClient.getUnknownHALTemplates().size()).isEqualTo(0); + assertThat(mNotificationSent).isFalse(); verify(mBiometricUtils, never()).removeBiometricForUser(any(), anyInt(), anyInt()); verify(mCallback).onClientFinished(mClient, true); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java index fd880dd23f25..178e7ec649c6 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -33,7 +33,6 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; -import android.platform.test.flag.junit.SetFlagsRule; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.DisplayInfo; @@ -41,13 +40,11 @@ import android.view.WindowManager; import androidx.test.InstrumentationRegistry; -import com.android.input.flags.Flags; import com.android.server.LocalServices; import com.android.server.input.InputManagerInternal; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -60,9 +57,6 @@ public class InputControllerTest { private static final String LANGUAGE_TAG = "en-US"; private static final String LAYOUT_TYPE = "qwerty"; - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); - @Mock private InputManagerInternal mInputManagerInternalMock; @Mock @@ -77,8 +71,6 @@ public class InputControllerTest { @Before public void setUp() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER); - MockitoAnnotations.initMocks(this); mInputManagerMockHelper = new InputManagerMockHelper( TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock); 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/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 2b81d78a9163..da8961df38a8 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -339,8 +339,6 @@ public class VirtualDeviceManagerServiceTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - mSetFlagsRule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER); - doNothing().when(mInputManagerInternalMock) .setMousePointerAccelerationEnabled(anyBoolean(), anyInt()); doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 855c6582dfec..b4cc3434e013 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -441,11 +441,6 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi @Override public void runCryptoSelfTest() {} - @Override - public String[] getPersonalAppsForSuspension(int userId) { - return new String[]{}; - } - public void setSystemCurrentTimeMillis(long value) { mCurrentTimeMillis = value; } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java index be5e2623ac20..c1ae85252ffc 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ActiveSourceActionTest.java @@ -24,8 +24,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.os.Looper; import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; @@ -72,6 +74,11 @@ public class ActiveSourceActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java index 5be3c8e4671c..a5f7bb117e7d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcInitiationActionFromAvrTest.java @@ -22,8 +22,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; import android.os.test.TestLooper; @@ -84,6 +86,11 @@ public class ArcInitiationActionFromAvrTest { protected Looper getServiceLooper() { return mTestLooper.getLooper(); } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java index 7845c307c15f..857ee1aa176f 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java @@ -22,8 +22,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; @@ -90,6 +92,11 @@ public class ArcTerminationActionFromAvrTest { protected Looper getServiceLooper() { return mTestLooper.getLooper(); } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java index 98789ac96e98..6ace9f14757c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java @@ -33,8 +33,10 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; @@ -140,17 +142,8 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { // do nothing } - /** - * Override displayOsd to prevent it from broadcasting an intent, which - * can trigger a SecurityException. - */ @Override - void displayOsd(int messageId) { - // do nothing - } - - @Override - void displayOsd(int messageId, int extra) { + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { // do nothing } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java index 9b65762e48ec..2dd593c8cb29 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DetectTvSystemAudioModeSupportActionTest.java @@ -18,6 +18,8 @@ package com.android.server.hdmi; import static org.junit.Assert.assertEquals; import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; @@ -103,6 +105,11 @@ public class DetectTvSystemAudioModeSupportActionTest { protected Looper getServiceLooper() { return mTestLooper.getLooper(); } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(hdmiControlService) { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java index 922706e16a75..e669e7c019d6 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DevicePowerStatusActionTest.java @@ -25,8 +25,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -90,6 +92,11 @@ public class DevicePowerStatusActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java index 68ef80fa62c9..29d20a64e439 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromPlaybackTest.java @@ -30,7 +30,9 @@ import static com.android.server.hdmi.DeviceSelectActionFromPlayback.STATE_WAIT_ import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -123,6 +125,11 @@ public class DeviceSelectActionFromPlaybackTest { boolean isPowerStandby() { return false; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java index 26b448a491f2..d32b75bc57da 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/DeviceSelectActionFromTvTest.java @@ -29,7 +29,9 @@ import static com.android.server.hdmi.DeviceSelectActionFromTv.STATE_WAIT_FOR_RE import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -132,6 +134,11 @@ public class DeviceSelectActionFromTvTest { boolean isPowerStandby() { return false; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java index a621055a99eb..c7574bdc3f6c 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java @@ -36,6 +36,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -98,6 +99,8 @@ public class HdmiCecAtomLoggingTest { audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter(); HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); doReturn(hdmiCecConfig).when(mHdmiControlServiceSpy).getHdmiCecConfig(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java index 30ce9616d9b5..e1e101fc1724 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTvTest.java @@ -19,6 +19,7 @@ import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING; import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_UNKNOWN; import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -30,6 +31,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -90,6 +92,8 @@ public class HdmiCecAtomLoggingTvTest { audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); doReturn(mHdmiCecAtomWriterSpy).when(mHdmiControlServiceSpy).getAtomWriter(); HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java index 0870bac6ef38..7ed596ea8bab 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecControllerTest.java @@ -48,6 +48,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Binder; @@ -110,6 +111,8 @@ public class HdmiCecControllerTest { doAnswer(__ -> mCecVersion).when(mHdmiControlServiceSpy).getCecVersion(); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); mHdmiControlServiceSpy.setDeviceConfig(new FakeDeviceConfigWrapper()); mNativeWrapper = new FakeNativeWrapper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java index 55208972895d..5502de8f46e9 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java @@ -26,7 +26,9 @@ import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -114,6 +116,11 @@ public class HdmiCecLocalDeviceAudioSystemTest { return defVal; } } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.getHdmiCecConfig().setIntValue( diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java index 28da97c58383..8df7d548e21e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java @@ -28,7 +28,9 @@ import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -138,6 +140,11 @@ public class HdmiCecLocalDevicePlaybackTest { boolean canGoToStandby() { return true; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java index 3dd83125619a..192be2a4200b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTest.java @@ -37,7 +37,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.tv.cec.V1_0.Result; @@ -169,6 +171,11 @@ public class HdmiCecLocalDeviceTest { void wakeUp() { mWakeupMessageReceived = true; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.setIoLooper(mTestLooper.getLooper()); mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context)); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java index 4faeea50c1e1..b50684bb7a25 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java @@ -26,6 +26,8 @@ import static com.android.server.hdmi.Constants.ADDR_TV; import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE; +import static com.android.server.hdmi.HdmiControlService.STANDBY_SCREEN_OFF; +import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON; import static com.google.common.truth.Truth.assertThat; @@ -41,7 +43,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -184,17 +188,8 @@ public class HdmiCecLocalDeviceTvTest { return mEarcBlocksArc; } - /** - * Override displayOsd to prevent it from broadcasting an intent, which - * can trigger a SecurityException. - */ @Override - void displayOsd(int messageId) { - // do nothing - } - - @Override - void displayOsd(int messageId, int extra) { + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { // do nothing } }; @@ -1787,9 +1782,17 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); @@ -1798,6 +1801,10 @@ public class HdmiCecLocalDeviceTvTest { mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); mTestLooper.dispatchAll(); + // Assume there was a retry and the action did not finish earlier. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); } @@ -1807,9 +1814,18 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); + + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); @@ -1834,8 +1850,18 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); mTestLooper.dispatchAll(); assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); @@ -1854,8 +1880,16 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.getHdmiCecNetwork().clearLocalDevices(); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS); + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder() .setLogicalAddress(ADDR_PLAYBACK_1) .setPhysicalAddress(0x1000) @@ -1869,6 +1903,10 @@ public class HdmiCecLocalDeviceTvTest { mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice); mTestLooper.dispatchAll(); + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); + mTestLooper.dispatchAll(); + assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource); mNativeWrapper.clearResultMessages(); mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null); @@ -1881,6 +1919,41 @@ public class HdmiCecLocalDeviceTvTest { assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); } + @Test + public void onAddressAllocated_sendSourceChangingMessage_noRequestActiveSourceMessage() { + HdmiCecMessage requestActiveSource = + HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV); + HdmiCecMessage activeSourceFromTv = + HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); + HdmiCecMessage setStreamPathFromTv = + HdmiCecMessageBuilder.buildSetStreamPath(ADDR_TV, 0x2000); + + // Go to standby to invalidate the active source on the local device s.t. the + // RequestActiveSourceAction will start. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); + mTestLooper.dispatchAll(); + + // Even if the device at the end of this path doesn't answer to this message, TV should not + // continue the RequestActiveSourceAction. + mHdmiControlService.sendCecCommand(setStreamPathFromTv); + + // Skip the LauncherX API timeout. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestActiveSource); + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + // Assume there was a retry and the action did not finish earlier. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv); + } @Test public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() { @@ -1907,7 +1980,12 @@ public class HdmiCecLocalDeviceTvTest { HdmiCecMessage activeSourceFromTv = HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000); - mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_WAKE_UP_MESSAGE); + // Go to standby to invalidate the active source on the local device s.t. the + // TV will send <Active Source> when it selects its internal source. + mHdmiControlService.onStandby(STANDBY_SCREEN_OFF); + mTestLooper.dispatchAll(); + + mHdmiControlService.onWakeUp(WAKE_UP_SCREEN_ON); mTestLooper.dispatchAll(); mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS); @@ -1937,6 +2015,8 @@ public class HdmiCecLocalDeviceTvTest { public void handleStandby_fromActiveSource_standby() { mPowerManager.setInteractive(true); mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + mHdmiControlService.setActiveSource(ADDR_PLAYBACK_1, 0x1000, "HdmiCecLocalDeviceTvTest"); mTestLooper.dispatchAll(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java index c002067ae9e7..9412ee0d4ac7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecPowerStatusControllerTest.java @@ -21,8 +21,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -88,6 +90,11 @@ public class HdmiCecPowerStatusControllerTest { boolean isPowerStandby() { return false; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 9a92c704c247..126a65863f59 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -52,6 +52,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -106,6 +107,7 @@ public class HdmiControlServiceTest { private HdmiPortInfo[] mHdmiPortInfo; private ArrayList<Integer> mLocalDeviceTypes = new ArrayList<>(); private static final int PORT_ID_EARC_SUPPORTED = 3; + private static final int EARC_TRIGGER_START_ARC_ACTION_DELAY = 500; @Before public void setUp() throws Exception { @@ -120,6 +122,8 @@ public class HdmiControlServiceTest { audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); mMyLooper = mTestLooper.getLooper(); @@ -1374,6 +1378,11 @@ public class HdmiControlServiceTest { PORT_ID_EARC_SUPPORTED); verify(mHdmiControlServiceSpy, times(1)) .notifyEarcStatusToAudioService(eq(false), eq(new ArrayList<>())); + // ARC should be never initiated here. It should be started after 500 ms. + verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any()); + // We move 500 ms forward because the action is only started 500 ms later. + mTestLooper.moveTimeForward(EARC_TRIGGER_START_ARC_ACTION_DELAY); + mTestLooper.dispatchAll(); verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(true), any()); } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java index 46418026e540..298ff460c9eb 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/OneTouchPlayActionTest.java @@ -26,8 +26,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -104,6 +106,11 @@ public class OneTouchPlayActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java index 9f0a44ce008a..1d4a72fc30e2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/PowerStatusMonitorActionTest.java @@ -25,8 +25,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; @@ -78,6 +80,11 @@ public class PowerStatusMonitorActionTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java index 043db1eb298d..cafe1e7dc197 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RequestSadActionTest.java @@ -21,7 +21,9 @@ import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM; import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.os.Looper; @@ -115,6 +117,11 @@ public class RequestSadActionTest { boolean isPowerStandbyOrTransient() { return false; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.setIoLooper(mMyLooper); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java index 061e1f90fa58..864a182e8d0d 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionPlaybackTest.java @@ -21,7 +21,9 @@ import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_ import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; @@ -72,6 +74,11 @@ public class ResendCecCommandActionPlaybackTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mIsPowerStandby = false; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java index b25ea2c5078c..06709cdd6ac4 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/ResendCecCommandActionTvTest.java @@ -21,7 +21,9 @@ import static com.android.server.hdmi.ResendCecCommandAction.SEND_COMMAND_RETRY_ import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; @@ -75,6 +77,11 @@ public class ResendCecCommandActionTvTest { boolean verifyPhysicalAddresses(HdmiCecMessage message) { return true; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mMyLooper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java index f608c235b0be..5163e29b86f1 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/RoutingControlActionTest.java @@ -29,7 +29,9 @@ import static com.android.server.hdmi.RoutingControlAction.STATE_WAIT_FOR_ROUTIN import static com.google.common.truth.Truth.assertThat; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; import android.hardware.hdmi.IHdmiControlCallback; @@ -177,6 +179,11 @@ public class RoutingControlActionTest { protected HdmiCecConfig getHdmiCecConfig() { return hdmiCecConfig; } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; mHdmiControlService.setIoLooper(mMyLooper); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java index a73f4aa35cf9..e4297effed92 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SetAudioVolumeLevelDiscoveryActionTest.java @@ -24,12 +24,14 @@ import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; @@ -87,6 +89,8 @@ public class SetAudioVolumeLevelDiscoveryActionTest { audioFramework.getAudioManager(), audioFramework.getAudioDeviceVolumeManager())); doNothing().when(mHdmiControlServiceSpy) .writeStringSystemProperty(anyString(), anyString()); + doNothing().when(mHdmiControlServiceSpy) + .sendBroadcastAsUser(any(Intent.class)); mLooper = mTestLooper.getLooper(); mHdmiControlServiceSpy.setIoLooper(mLooper); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java index 02bed229d181..4dcc6a400c1e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioAutoInitiationActionTest.java @@ -25,8 +25,10 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; +import android.annotation.RequiresPermission; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.HdmiPortInfo; import android.os.Looper; @@ -82,17 +84,8 @@ public class SystemAudioAutoInitiationActionTest { // do nothing } - /** - * Override displayOsd to prevent it from broadcasting an intent, which - * can trigger a SecurityException. - */ @Override - void displayOsd(int messageId) { - // do nothing - } - - @Override - void displayOsd(int messageId, int extra) { + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { // do nothing } }; diff --git a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java index f801f8853980..4aa074b1a52b 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/SystemAudioInitiationActionFromAvrTest.java @@ -15,13 +15,17 @@ */ package com.android.server.hdmi; +import static com.android.server.hdmi.HdmiConfig.TIMEOUT_MS; + import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.content.Context; +import android.content.Intent; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Looper; @@ -60,6 +64,7 @@ public class SystemAudioInitiationActionFromAvrTest { private boolean mArcEnabled; private boolean mIsPlaybackDevice; private boolean mBroadcastActiveSource; + private boolean mStandbyMessageReceived; @Before public void SetUp() { @@ -135,6 +140,16 @@ public class SystemAudioInitiationActionFromAvrTest { int pathToPortId(int path) { return -1; } + + @Override + protected boolean isStandbyMessageReceived() { + return mStandbyMessageReceived; + } + + @Override + protected void sendBroadcastAsUser(@RequiresPermission Intent intent) { + // do nothing + } }; Looper looper = mTestLooper.getLooper(); @@ -286,6 +301,22 @@ public class SystemAudioInitiationActionFromAvrTest { assertThat(mHdmiCecLocalDeviceAudioSystem.isSystemAudioActivated()).isTrue(); } + @Test + public void onActionStarted_deviceGoesToSleep_noActiveSourceAfterTimeout() { + resetTestVariables(); + + mStandbyMessageReceived = true; + mHdmiCecLocalDeviceAudioSystem.addAndStartAction( + new SystemAudioInitiationActionFromAvr( + mHdmiCecLocalDeviceAudioSystem)); + mTestLooper.dispatchAll(); + + mTestLooper.moveTimeForward(TIMEOUT_MS); + mTestLooper.dispatchAll(); + + assertThat(mBroadcastActiveSource).isFalse(); + } + private void resetTestVariables() { mMsgRequestActiveSourceCount = 0; mMsgSetSystemAudioModeCount = 0; @@ -295,5 +326,6 @@ public class SystemAudioInitiationActionFromAvrTest { mBroadcastActiveSource = false; mHdmiCecLocalDeviceAudioSystem.getActiveSource().physicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; + mStandbyMessageReceived = false; } } 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/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java index 07fb9fc2f509..570256bf43e6 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkManagementServiceTest.java @@ -19,9 +19,16 @@ package com.android.server.net; import static android.net.ConnectivityManager.FIREWALL_CHAIN_BACKGROUND; import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_ADMIN; +import static android.net.ConnectivityManager.FIREWALL_CHAIN_METERED_DENY_USER; import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE; import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED; import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY; +import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW; +import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT; +import static android.net.ConnectivityManager.FIREWALL_RULE_DENY; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT; import static android.util.DebugUtils.valueToString; import static org.junit.Assert.assertEquals; @@ -51,7 +58,10 @@ import android.os.PermissionEnforcer; import android.os.Process; import android.os.RemoteException; import android.os.test.FakePermissionEnforcer; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.ArrayMap; import androidx.test.filters.SmallTest; @@ -62,6 +72,7 @@ import com.android.modules.utils.build.SdkLevel; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -84,6 +95,9 @@ public class NetworkManagementServiceTest { @Mock private IBatteryStats.Stub mBatteryStatsService; @Mock private INetd.Stub mNetdService; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT); + private static final int TEST_UID = 111; @NonNull @@ -254,6 +268,7 @@ public class NetworkManagementServiceTest { } @Test + @DisableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) public void testMeteredNetworkRestrictions() throws RemoteException { // Make sure the mocked netd method returns true. doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean()); @@ -295,6 +310,69 @@ public class NetworkManagementServiceTest { } @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testMeteredNetworkRestrictionsByAdminChain() { + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DENY); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DENY); + assertTrue("Should be true since mobile data usage is restricted by admin chain", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_ADMIN, TEST_UID, + FIREWALL_RULE_DEFAULT); + assertFalse("Should be false since mobile data usage is no longer restricted by admin", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testMeteredNetworkRestrictionsByUserChain() { + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DENY); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DENY); + assertTrue("Should be true since mobile data usage is restricted by user chain", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_DENY_USER, TEST_UID, + FIREWALL_RULE_DEFAULT); + assertFalse("Should be false since mobile data usage is no longer restricted by user", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test + @EnableFlags(Flags.FLAG_USE_METERED_FIREWALL_CHAINS) + public void testDataSaverRestrictionsWithAllowChain() { + mNMService.setDataSaverModeEnabled(true); + verify(mCm).setDataSaverEnabled(true); + + assertTrue("Should be true since data saver is on and the uid is not allowlisted", + mNMService.isNetworkRestricted(TEST_UID)); + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, FIREWALL_RULE_ALLOW); + assertFalse("Should be false since data saver is on and the uid is allowlisted", + mNMService.isNetworkRestricted(TEST_UID)); + + // remove uid from allowlist and turn datasaver off again + + mNMService.setFirewallUidRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, + FIREWALL_RULE_DEFAULT); + verify(mCm).setUidFirewallRule(FIREWALL_CHAIN_METERED_ALLOW, TEST_UID, + FIREWALL_RULE_DEFAULT); + mNMService.setDataSaverModeEnabled(false); + verify(mCm).setDataSaverEnabled(false); + + assertFalse("Network should not be restricted when data saver is off", + mNMService.isNetworkRestricted(TEST_UID)); + } + + @Test public void testFirewallChains() { final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>(); // Dozable chain diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java index 9862663c37b2..1db97b9ede81 100644 --- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java @@ -186,7 +186,8 @@ public class BugreportManagerServiceImplTest { new FileDescriptor(), /* screenshotFd= */ null, BugreportParams.BUGREPORT_MODE_FULL, /* flags= */ 0, new Listener(new CountDownLatch(1)), - /* isScreenshotRequested= */ false); + /* isScreenshotRequested= */ false, + /* skipUserConsentUnused = */ false); assertThat(mInjector.isBugreportStarted()).isTrue(); } @@ -202,7 +203,8 @@ public class BugreportManagerServiceImplTest { new FileDescriptor(), /* screenshotFd= */ null, BugreportParams.BUGREPORT_MODE_FULL, /* flags= */ 0, new Listener(new CountDownLatch(1)), - /* isScreenshotRequested= */ false); + /* isScreenshotRequested= */ false, + /* skipUserConsentUnused = */ false); assertThat(mInjector.isBugreportStarted()).isTrue(); } @@ -216,7 +218,8 @@ public class BugreportManagerServiceImplTest { new FileDescriptor(), /* screenshotFd= */ null, BugreportParams.BUGREPORT_MODE_FULL, /* flags= */ 0, new Listener(new CountDownLatch(1)), - /* isScreenshotRequested= */ false)); + /* isScreenshotRequested= */ false, + /* skipUserConsentUnused = */ false)); assertThat(thrown.getMessage()).contains("not an admin user"); } @@ -232,7 +235,8 @@ public class BugreportManagerServiceImplTest { new FileDescriptor(), /* screenshotFd= */ null, BugreportParams.BUGREPORT_MODE_REMOTE, /* flags= */ 0, new Listener(new CountDownLatch(1)), - /* isScreenshotRequested= */ false)); + /* isScreenshotRequested= */ false, + /* skipUserConsentUnused = */ false)); assertThat(thrown.getMessage()).contains("not affiliated to the device owner"); } @@ -243,7 +247,7 @@ public class BugreportManagerServiceImplTest { Listener listener = new Listener(latch); mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(), mContext.getUserId(), new FileDescriptor(), mBugreportFile, - /* keepOnRetrieval= */ false, listener); + /* keepOnRetrieval= */ false, /* skipUserConsent = */ false, listener); assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(listener.getErrorCode()).isEqualTo( BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE); diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java index 9fc46c563841..2f3bca031f46 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java @@ -1786,10 +1786,10 @@ public final class DataManagerTest { /* matchesInterruptionFilter= */ false, /* visibilityOverride= */ 0, /* suppressedVisualEffects= */ 0, - mParentNotificationChannel.getImportance(), + mNotificationChannel.getImportance(), /* explanation= */ null, /* overrideGroupKey= */ null, - mParentNotificationChannel, + mNotificationChannel, /* overridePeople= */ null, /* snoozeCriteria= */ null, /* showBadge= */ true, diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java index 5902caae443d..d0acaccf1de2 100644 --- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -26,12 +26,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; @@ -39,9 +42,12 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.NonNull; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.content.Context; +import android.hardware.power.ChannelConfig; +import android.hardware.power.IPower; import android.hardware.power.SessionConfig; import android.hardware.power.SessionTag; import android.hardware.power.WorkDuration; @@ -50,6 +56,7 @@ import android.os.IBinder; import android.os.IHintSession; import android.os.PerformanceHintManager; import android.os.Process; +import android.os.RemoteException; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; @@ -130,12 +137,15 @@ public class HintManagerServiceTest { @Mock private HintManagerService.NativeWrapper mNativeWrapperMock; @Mock + private IPower mIPowerMock; + @Mock private ActivityManagerInternal mAmInternalMock; @Rule public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); private HintManagerService mService; + private ChannelConfig mConfig; private static Answer<Long> fakeCreateWithConfig(Long ptr, Long sessionId) { return new Answer<Long>() { @@ -149,6 +159,9 @@ public class HintManagerServiceTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mConfig = new ChannelConfig(); + mConfig.readFlagBitmask = 1; + mConfig.writeFlagBitmask = 2; when(mNativeWrapperMock.halGetHintSessionPreferredRate()) .thenReturn(DEFAULT_HINT_PREFERRED_RATE); when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_A), @@ -170,6 +183,8 @@ public class HintManagerServiceTest { any(SessionConfig.class))).thenAnswer(fakeCreateWithConfig(SESSION_PTRS[2], SESSION_IDS[2])); + when(mIPowerMock.getInterfaceVersion()).thenReturn(5); + when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock); } @@ -252,6 +267,9 @@ public class HintManagerServiceTest { NativeWrapper createNativeWrapper() { return mNativeWrapperMock; } + IPower createIPower() { + return mIPowerMock; + } }); return mService; } @@ -261,6 +279,9 @@ public class HintManagerServiceTest { NativeWrapper createNativeWrapper() { return new NativeWrapperFake(); } + IPower createIPower() { + return mIPowerMock; + } }); return mService; } @@ -728,6 +749,102 @@ public class HintManagerServiceTest { verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean()); } + @Test + public void testGetChannel() throws Exception { + HintManagerService service = createService(); + Binder token = new Binder(); + + // Should only call once, after caching the first call + ChannelConfig config = service.getBinderServiceInstance().getSessionChannel(token); + ChannelConfig config2 = service.getBinderServiceInstance().getSessionChannel(token); + verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); + assertEquals(config.readFlagBitmask, mConfig.readFlagBitmask); + assertEquals(config.writeFlagBitmask, mConfig.writeFlagBitmask); + assertEquals(config2.readFlagBitmask, mConfig.readFlagBitmask); + assertEquals(config2.writeFlagBitmask, mConfig.writeFlagBitmask); + } + + @Test + public void testGetChannelTwice() throws Exception { + HintManagerService service = createService(); + Binder token = new Binder(); + + service.getBinderServiceInstance().getSessionChannel(token); + verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); + service.getBinderServiceInstance().closeSessionChannel(); + verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); + + clearInvocations(mIPowerMock); + + service.getBinderServiceInstance().getSessionChannel(token); + verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); + service.getBinderServiceInstance().closeSessionChannel(); + verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); + } + + @Test + public void testGetChannelFails() throws Exception { + HintManagerService service = createService(); + Binder token = new Binder(); + + when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenThrow(RemoteException.class); + + assertThrows(IllegalStateException.class, () -> { + service.getBinderServiceInstance().getSessionChannel(token); + }); + } + + + @Test + public void testGetChannelBadVersion() throws Exception { + when(mIPowerMock.getInterfaceVersion()).thenReturn(3); + HintManagerService service = createService(); + Binder token = new Binder(); + + reset(mIPowerMock); + when(mIPowerMock.getInterfaceVersion()).thenReturn(3); + when(mIPowerMock.getSessionChannel(anyInt(), anyInt())).thenReturn(mConfig); + + ChannelConfig channel = service.getBinderServiceInstance().getSessionChannel(token); + verify(mIPowerMock, times(0)).getSessionChannel(eq(TGID), eq(UID)); + assertNull(channel); + } + + @Test + public void testCloseChannel() throws Exception { + HintManagerService service = createService(); + Binder token = new Binder(); + + service.getBinderServiceInstance().getSessionChannel(token); + service.getBinderServiceInstance().closeSessionChannel(); + verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); + } + + @Test + public void testCloseChannelFails() throws Exception { + HintManagerService service = createService(); + Binder token = new Binder(); + + service.getBinderServiceInstance().getSessionChannel(token); + + doThrow(RemoteException.class).when(mIPowerMock).closeSessionChannel(anyInt(), anyInt()); + + assertThrows(IllegalStateException.class, () -> { + service.getBinderServiceInstance().closeSessionChannel(); + }); + } + + @Test + public void testDoubleClose() throws Exception { + HintManagerService service = createService(); + Binder token = new Binder(); + + service.getBinderServiceInstance().getSessionChannel(token); + service.getBinderServiceInstance().closeSessionChannel(); + service.getBinderServiceInstance().closeSessionChannel(); + verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); + } + // This test checks that concurrent operations from different threads on IHintService, // IHintSession and UidObserver will not cause data race or deadlock. Ideally we should also // check the output of threads' reportActualDuration performance to detect lock starvation @@ -935,4 +1052,40 @@ public class HintManagerServiceTest { a.reportActualWorkDuration2(WORK_DURATIONS_FIVE); verify(mNativeWrapperMock, never()).halReportActualWorkDuration(anyLong(), any(), any()); } + + @Test + public void testChannelDiesWhenTokenDies() throws Exception { + HintManagerService service = createService(); + + class DyingToken extends Binder { + DeathRecipient mToNotify; + @Override + public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { + mToNotify = recipient; + super.linkToDeath(recipient, flags); + } + + public void fakeDeath() { + mToNotify.binderDied(); + } + } + + DyingToken token = new DyingToken(); + + service.getBinderServiceInstance().getSessionChannel(token); + verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); + assertTrue(service.hasChannel(TGID, UID)); + + token.fakeDeath(); + + assertFalse(service.hasChannel(TGID, UID)); + verify(mIPowerMock, times(1)).closeSessionChannel(eq(TGID), eq(UID)); + + clearInvocations(mIPowerMock); + + token = new DyingToken(); + service.getBinderServiceInstance().getSessionChannel(token); + verify(mIPowerMock, times(1)).getSessionChannel(eq(TGID), eq(UID)); + assertTrue(service.hasChannel(TGID, UID)); + } } diff --git a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java index 06726b031a36..55d93fb8d9dc 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppIdleHistoryTests.java @@ -144,7 +144,9 @@ public class AppIdleHistoryTests extends AndroidTestCase { assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000), REASON_MAIN_FORCED_BY_USER); - assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE)); + if (!Flags.avoidIdleCheck()) { + assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE)); + } assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE)); assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_FREQUENT)); } @@ -243,4 +245,4 @@ public class AppIdleHistoryTests extends AndroidTestCase { expectedExpiryTimeMs, actualExpiryTimeMs); } } -}
\ No newline at end of file +} diff --git a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp index 131b380d9215..3def48aefa00 100644 --- a/services/tests/servicestests/test-apps/PackageParserApp/Android.bp +++ b/services/tests/servicestests/test-apps/PackageParserApp/Android.bp @@ -116,3 +116,20 @@ android_test_helper_app { resource_dirs: ["res"], manifest: "AndroidManifestApp7.xml", } + +android_test_helper_app { + name: "PackageParserTestApp8", + sdk_version: "current", + srcs: ["**/*.java"], + dex_preopt: { + enabled: false, + }, + optimize: { + enabled: false, + }, + resource_dirs: ["res"], + aaptflags: [ + "--feature-flags my.flag1,my.flag2,my.flag3,my.flag4,unknown.flag", + ], + manifest: "AndroidManifestApp8.xml", +} diff --git a/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml new file mode 100644 index 000000000000..d489c1bb9e07 --- /dev/null +++ b/services/tests/servicestests/test-apps/PackageParserApp/AndroidManifestApp8.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.servicestests.apps.packageparserapp" > + + <application> + <activity android:name=".TestActivity" + android:exported="true" /> + </application> + + <permission android:name="PERM1" android:featureFlag="my.flag1 " /> + <permission android:name="PERM2" android:featureFlag=" !my.flag2" /> + <permission android:name="PERM3" android:featureFlag="my.flag3" /> + <permission android:name="PERM4" android:featureFlag="!my.flag4" /> + <permission android:name="PERM5" android:featureFlag="unknown.flag" /> +</manifest>
\ No newline at end of file diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index 1194973b4cad..c7c97e40b424 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -34,6 +34,7 @@ import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; @@ -54,6 +55,7 @@ import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.service.notification.StatusBarNotification; @@ -271,7 +273,8 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testAddSummary() { + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAddSummary_alwaysAutogroup() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { mGroupHelper.onNotificationPosted( @@ -279,13 +282,52 @@ public class GroupHelperTest extends UiServiceTestCase { } verify(mCallback, times(1)).addAutoGroupSummary( anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAddSummary() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { + assertThat(mGroupHelper.onNotificationPosted( + getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false)).isFalse(); + } + assertThat(mGroupHelper.onNotificationPosted( + getSbn(pkg, AUTOGROUP_AT_COUNT - 1, String.valueOf(AUTOGROUP_AT_COUNT - 1), + UserHandle.SYSTEM), false)).isTrue(); + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); + } + + @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAddSummary_oneChildOngoing_summaryOngoing_alwaysAutogroup() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + } + mGroupHelper.onNotificationPosted(sbn, false); + } + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT))); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); } @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) public void testAddSummary_oneChildOngoing_summaryOngoing() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -297,13 +339,33 @@ public class GroupHelperTest extends UiServiceTestCase { } verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS | FLAG_ONGOING_EVENT))); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); + } + + @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel_alwaysAutogroup() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + } + mGroupHelper.onNotificationPosted(sbn, false); + } + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); } @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -315,13 +377,31 @@ public class GroupHelperTest extends UiServiceTestCase { } verify(mCallback, times(1)).addAutoGroupSummary( anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); + } + + @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel_alwaysAutogroup() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + mGroupHelper.onNotificationPosted(sbn, false); + } + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL))); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); } @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -331,13 +411,34 @@ public class GroupHelperTest extends UiServiceTestCase { } verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL))); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); + } + + @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAddSummary_summaryAutoCancelNoClear_alwaysAutogroup() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + if (i == 0) { + sbn.getNotification().flags |= FLAG_NO_CLEAR; + } + mGroupHelper.onNotificationPosted(sbn, false); + } + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR))); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); } @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) public void testAddSummary_summaryAutoCancelNoClear() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -350,7 +451,7 @@ public class GroupHelperTest extends UiServiceTestCase { } verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR))); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), any()); @@ -617,7 +718,7 @@ public class GroupHelperTest extends UiServiceTestCase { } verify(mCallback, times(1)).addAutoGroupSummary( anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); Mockito.reset(mCallback); @@ -645,7 +746,7 @@ public class GroupHelperTest extends UiServiceTestCase { } verify(mCallback, times(1)).addAutoGroupSummary( anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); Mockito.reset(mCallback); @@ -664,6 +765,47 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() { + final String pkg = "package"; + List<StatusBarNotification> posted = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + posted.add(sbn); + mGroupHelper.onNotificationPosted(sbn, false); + } + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + Mockito.reset(mCallback); + + for (int i = posted.size() - 2; i >= 0; i--) { + mGroupHelper.onNotificationRemoved(posted.remove(i)); + } + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + Mockito.reset(mCallback); + + // only one child remains + assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg)); + + // Add new notification; it should be autogrouped even though the total count is + // < AUTOGROUP_AT_COUNT + final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM); + posted.add(sbn); + assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isFalse(); + verify(mCallback, times(1)).addAutoGroup(sbn.getKey(), true); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), + eq(getNotificationAttributes(BASE_FLAGS))); + verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(), any()); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); @@ -672,9 +814,10 @@ public class GroupHelperTest extends UiServiceTestCase { posted.add(sbn); mGroupHelper.onNotificationPosted(sbn, false); } + verify(mCallback, times(1)).addAutoGroupSummary( anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS))); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); Mockito.reset(mCallback); @@ -693,8 +836,9 @@ public class GroupHelperTest extends UiServiceTestCase { // < AUTOGROUP_AT_COUNT final StatusBarNotification sbn = getSbn(pkg, 5, String.valueOf(5), UserHandle.SYSTEM); posted.add(sbn); - mGroupHelper.onNotificationPosted(sbn, true); - verify(mCallback, times(1)).addAutoGroup(sbn.getKey()); + assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isTrue(); + // addAutoGroup not called on sbn, because the autogrouping is expected to be done + // synchronously. verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), @@ -703,7 +847,42 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) + public void testAddSummary_sameIcon_sameColor_alwaysAutogroup() { + final String pkg = "package"; + final Icon icon = mock(Icon.class); + when(icon.sameAs(icon)).thenReturn(true); + final int iconColor = Color.BLUE; + final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor, + DEFAULT_VISIBILITY); + + // Add notifications with same icon and color + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null, + icon, iconColor); + mGroupHelper.onNotificationPosted(sbn, false); + } + // Check that the summary would have the same icon and color + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(attr)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + + // After auto-grouping, add new notification with the same color + StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT, + String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor); + mGroupHelper.onNotificationPosted(sbn, true); + + // Check that the summary was updated + //NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor); + verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(attr)); + } + + @Test + @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE, + android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST}) public void testAddSummary_sameIcon_sameColor() { final String pkg = "package"; final Icon icon = mock(Icon.class); @@ -721,7 +900,7 @@ public class GroupHelperTest extends UiServiceTestCase { // Check that the summary would have the same icon and color verify(mCallback, times(1)).addAutoGroupSummary( anyInt(), eq(pkg), anyString(), eq(attr)); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -761,7 +940,7 @@ public class GroupHelperTest extends UiServiceTestCase { // Check that the summary would have the same icon and color verify(mCallback, times(1)).addAutoGroupSummary( anyInt(), eq(pkg), anyString(), eq(initialAttr)); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -780,8 +959,9 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) - public void testAddSummary_diffVisibility() { + public void testAddSummary_diffVisibility_alwaysAutogroup() { final String pkg = "package"; final Icon icon = mock(Icon.class); when(icon.sameAs(icon)).thenReturn(true); @@ -798,7 +978,8 @@ public class GroupHelperTest extends UiServiceTestCase { // Check that the summary has private visibility verify(mCallback, times(1)).addAutoGroupSummary( anyInt(), eq(pkg), anyString(), eq(attr)); - verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(), anyBoolean()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -815,6 +996,48 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test + @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE, + android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST}) + public void testAddSummary_diffVisibility() { + final String pkg = "package"; + final Icon icon = mock(Icon.class); + when(icon.sameAs(icon)).thenReturn(true); + final int iconColor = Color.BLUE; + final NotificationAttributes attr = new NotificationAttributes(BASE_FLAGS, icon, iconColor, + VISIBILITY_PRIVATE); + + // Add notifications with same icon and color and default visibility (private) + for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null, + icon, iconColor); + assertThat(mGroupHelper.onNotificationPosted(sbn, false)).isFalse(); + } + // The last notification added will reach the autogroup threshold. + StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT - 1, + String.valueOf(AUTOGROUP_AT_COUNT - 1), UserHandle.SYSTEM, null, icon, iconColor); + assertThat(mGroupHelper.onNotificationPosted(sbn, false)).isTrue(); + + // Check that the summary has private visibility + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(attr)); + // The last sbn is expected to be added to autogroup synchronously. + verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + + // After auto-grouping, add new notification with public visibility + sbn = getSbn(pkg, AUTOGROUP_AT_COUNT, + String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, icon, iconColor); + sbn.getNotification().visibility = VISIBILITY_PUBLIC; + assertThat(mGroupHelper.onNotificationPosted(sbn, true)).isTrue(); + + // Check that the summary visibility was updated + NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, icon, iconColor, + VISIBILITY_PUBLIC); + verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr)); + } + + @Test @EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE) public void testAutoGrouped_diffIcon_diffColor_removeChild_updateTo_sameIcon_sameColor() { final String pkg = "package"; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java index 4af20a97a9aa..70a003814036 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java @@ -24,6 +24,8 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; +import static android.content.pm.PackageManager.PERMISSION_DENIED; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.media.AudioAttributes.USAGE_NOTIFICATION; import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE; @@ -52,6 +54,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.Manifest.permission; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; @@ -67,6 +70,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.AudioAttributes; @@ -93,6 +97,7 @@ import android.view.accessibility.IAccessibilityManagerClient; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.R; import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags; import com.android.internal.config.sysui.TestableFlagResolver; import com.android.internal.logging.InstanceIdSequence; @@ -188,6 +193,8 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { getContext().addMockSystemService(Vibrator.class, mVibrator); getContext().addMockSystemService(PackageManager.class, mPackageManager); when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(false); + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + anyString())).thenReturn(PERMISSION_DENIED); when(mAudioManager.isAudioFocusExclusive()).thenReturn(false); when(mAudioManager.getRingtonePlayer()).thenReturn(mRingtonePlayer); @@ -210,6 +217,16 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { verify(mAccessibilityService).addClient(any(IAccessibilityManagerClient.class), anyInt()); assertTrue(mAccessibilityManager.isEnabled()); + // Enable LED pulse setting by default + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE, 1); + + Resources resources = spy(getContext().getResources()); + when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(true); + when(resources.getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true); + when(getContext().getResources()).thenReturn(resources); + // TODO (b/291907312): remove feature flag // Disable feature flags by default. Tests should enable as needed. mSetFlagsRule.disableFlags(Flags.FLAG_POLITE_NOTIFICATIONS, @@ -239,7 +256,6 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { mAttentionHelper.setKeyguardManager(mKeyguardManager); mAttentionHelper.setScreenOn(false); mAttentionHelper.setInCallStateOffHook(false); - mAttentionHelper.mNotificationPulseEnabled = true; if (Flags.crossAppPoliteNotifications()) { // Capture BroadcastReceiver for avalanche triggers @@ -611,6 +627,14 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt()); } + private void verifyAttentionLights() { + verify(mLight, times(1)).pulse(); + } + + private void verifyNeverAttentionLights() { + verify(mLight, never()).pulse(); + } + // // Tests // @@ -1524,7 +1548,10 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { @Test public void testLightsLightsOffGlobally() { - mAttentionHelper.mNotificationPulseEnabled = false; + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_LIGHT_PULSE, 0); + initAttentionHelper(mTestFlagResolver); + NotificationRecord r = getLightsNotification(); mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); verifyNeverLights(); @@ -1533,6 +1560,44 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test + public void testLightsLightsResConfigDisabled() { + Resources resources = spy(getContext().getResources()); + when(resources.getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(false); + when(getContext().getResources()).thenReturn(resources); + initAttentionHelper(mTestFlagResolver); + + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverLights(); + assertFalse(r.isInterruptive()); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsUseAttentionLight() { + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyAttentionLights(); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test + public void testLightsUseAttentionLightDisabled() { + Resources resources = spy(getContext().getResources()); + when(resources.getBoolean(R.bool.config_useAttentionLight)).thenReturn(false); + when(resources.getBoolean( + com.android.internal.R.bool.config_intrusiveNotificationLed)).thenReturn(true); + when(getContext().getResources()).thenReturn(resources); + initAttentionHelper(mTestFlagResolver); + + NotificationRecord r = getLightsNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyNeverAttentionLights(); + assertEquals(-1, r.getLastAudiblyAlertedMs()); + } + + @Test public void testLightsDndIntercepted() { NotificationRecord r = getLightsNotification(); r.setSuppressedVisualEffects(SUPPRESSED_EFFECT_LIGHTS); @@ -2303,6 +2368,72 @@ public class NotificationAttentionHelperTest extends UiServiceTestCase { } @Test + public void testBeepVolume_politeNotif_Avalanche_exemptEmergency() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + initAttentionHelper(flagResolver); + + // Trigger avalanche trigger intent + final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", false); + mAvalancheBroadcastReceiver.onReceive(getContext(), intent); + + NotificationRecord r = getBeepyNotification(); + + // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED); + + // Should beep at 100% volume + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + verifyBeepVolume(1.0f); + assertNotEquals(-1, r.getLastAudiblyAlertedMs()); + verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test + public void testBeepVolume_politeNotif_exemptEmergency() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); + mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); + mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS_ATTN_UPDATE); + TestableFlagResolver flagResolver = new TestableFlagResolver(); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME1, 50); + flagResolver.setFlagOverride(NotificationFlags.NOTIF_VOLUME2, 0); + // NOTIFICATION_COOLDOWN_ALL setting is enabled + Settings.System.putInt(getContext().getContentResolver(), + Settings.System.NOTIFICATION_COOLDOWN_ALL, 1); + initAttentionHelper(flagResolver); + + NotificationRecord r = getBeepyNotification(); + + // Grant RECEIVE_EMERGENCY_BROADCAST to notification's package + when(mPackageManager.checkPermission(eq(permission.RECEIVE_EMERGENCY_BROADCAST), + eq(r.getSbn().getPackageName()))).thenReturn(PERMISSION_GRANTED); + + // set up internal state + mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS); + Mockito.reset(mRingtonePlayer); + + // update should beep at 100% volume + NotificationRecord r2 = getBeepyNotification(); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + assertNotEquals(-1, r2.getLastAudiblyAlertedMs()); + verifyBeepVolume(1.0f); + + // 2nd update should beep at 100% volume + Mockito.reset(mRingtonePlayer); + mAttentionHelper.buzzBeepBlinkLocked(r2, DEFAULT_SIGNALS); + assertNotEquals(-1, r2.getLastAudiblyAlertedMs()); + verifyBeepVolume(1.0f); + + verify(mAccessibilityService, times(3)).sendAccessibilityEvent(any(), anyInt()); + } + + @Test public void testBeepVolume_politeNotif_applyPerApp() throws Exception { mSetFlagsRule.enableFlags(Flags.FLAG_POLITE_NOTIFICATIONS); mSetFlagsRule.disableFlags(Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS); 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 3a0eba11d488..e564ba6eb835 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -99,6 +99,7 @@ import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICAT import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; +import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; import static android.service.notification.NotificationListenerService.REASON_CANCEL; import static android.service.notification.NotificationListenerService.REASON_LOCKDOWN; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; @@ -112,6 +113,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; @@ -311,6 +313,7 @@ import com.android.server.wm.WindowManagerInternal; import com.google.android.collect.Lists; import com.google.common.collect.ImmutableList; + import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @@ -331,8 +334,6 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import platform.test.runner.parameterized.ParameterizedAndroidJunit4; -import platform.test.runner.parameterized.Parameters; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -340,14 +341,17 @@ 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.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CountDownLatch; import java.util.function.Consumer; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest @RunWith(ParameterizedAndroidJunit4.class) @RunWithLooper @@ -501,7 +505,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock MultiRateLimiter mToastRateLimiter; BroadcastReceiver mPackageIntentReceiver; - BroadcastReceiver mUserSwitchIntentReceiver; + BroadcastReceiver mUserIntentReceiver; BroadcastReceiver mNotificationTimeoutReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); TestableNotificationManagerService.StrongAuthTrackerFake mStrongAuthTracker; @@ -800,11 +804,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { mPackageIntentReceiver = broadcastReceivers.get(i); } - if (filter.hasAction(Intent.ACTION_USER_SWITCHED)) { + if (filter.hasAction(Intent.ACTION_USER_SWITCHED) + || filter.hasAction(Intent.ACTION_PROFILE_UNAVAILABLE) + || filter.hasAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) { // There may be multiple receivers, get the NMS one if (broadcastReceivers.get(i).toString().contains( NotificationManagerService.class.getName())) { - mUserSwitchIntentReceiver = broadcastReceivers.get(i); + mUserIntentReceiver = broadcastReceivers.get(i); } } if (filter.hasAction(ACTION_NOTIFICATION_TIMEOUT) @@ -813,7 +819,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); - assertNotNull("User-switch receiver should exist", mUserSwitchIntentReceiver); + assertNotNull("User receiver should exist", mUserIntentReceiver); if (!Flags.allNotifsNeedTtl()) { assertNotNull("Notification timeout receiver should exist", mNotificationTimeoutReceiver); @@ -906,7 +912,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } mService.clearNotifications(); - TestableLooper.get(this).processAllMessages(); + if (mTestableLooper != null) { + mTestableLooper.processAllMessages(); + } try { mService.onDestroy(); @@ -917,14 +925,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); } } @@ -974,7 +984,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private void simulateProfileAvailabilityActions(String intentAction) { final Intent intent = new Intent(intentAction); intent.putExtra(Intent.EXTRA_USER_HANDLE, TEST_PROFILE_USERHANDLE); - mUserSwitchIntentReceiver.onReceive(mContext, intent); + mUserIntentReceiver.onReceive(mContext, intent); } private ArrayMap<Boolean, ArrayList<ComponentName>> generateResetComponentValues() { @@ -1006,7 +1016,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, @@ -1299,6 +1311,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() { @@ -5542,7 +5654,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testAddAutogroup_requestsSort() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addNotification(r); - mService.addAutogroupKeyLocked(r.getKey()); + mService.addAutogroupKeyLocked(r.getKey(), true); verify(mRankingHandler, times(1)).requestSort(); } @@ -5562,12 +5674,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); r.setOverrideGroupKey("TEST"); mService.addNotification(r); - mService.addAutogroupKeyLocked(r.getKey()); + mService.addAutogroupKeyLocked(r.getKey(), true); + + verify(mRankingHandler, never()).requestSort(); + } + + @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAutogroupSuppressSort_noSort() throws Exception { + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(r); + mService.addAutogroupKeyLocked(r.getKey(), false); verify(mRankingHandler, never()).requestSort(); } @Test + @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST) + public void testAutogroupOnPost_skipManualSort() throws Exception { + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(r); + verify(mRankingHandler, never()).requestSort(); + } + + @Test public void testHandleRankingSort_sendsUpdateOnSignalExtractorChange() throws Exception { mService.setPreferencesHelper(mPreferencesHelper); NotificationManagerService.WorkerHandler handler = mock( @@ -5948,6 +6078,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(); } @@ -8777,6 +8909,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(); } @@ -14044,11 +14178,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))) @@ -14056,7 +14191,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(); @@ -14065,18 +14200,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))) @@ -14084,7 +14220,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(); @@ -14093,7 +14229,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 @@ -14462,13 +14599,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_USE_SSM_USER_SWITCH_SIGNAL) public void onUserSwitched_updatesZenModeAndChannelsBypassingDnd() { + mService.mZenModeHelper = mock(ZenModeHelper.class); + mService.setPreferencesHelper(mPreferencesHelper); + + UserInfo prevUser = new UserInfo(); + prevUser.id = 10; + UserInfo newUser = new UserInfo(); + newUser.id = 20; + + mService.onUserSwitching(new TargetUser(prevUser), new TargetUser(newUser)); + + InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper); + inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20)); + inOrder.verify(mPreferencesHelper).syncChannelsBypassingDnd(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @DisableFlags(Flags.FLAG_USE_SSM_USER_SWITCH_SIGNAL) + public void onUserSwitched_broadcast_updatesZenModeAndChannelsBypassingDnd() { Intent intent = new Intent(Intent.ACTION_USER_SWITCHED); intent.putExtra(Intent.EXTRA_USER_HANDLE, 20); mService.mZenModeHelper = mock(ZenModeHelper.class); mService.setPreferencesHelper(mPreferencesHelper); - mUserSwitchIntentReceiver.onReceive(mContext, intent); + mUserIntentReceiver.onReceive(mContext, intent); InOrder inOrder = inOrder(mPreferencesHelper, mService.mZenModeHelper); inOrder.verify(mService.mZenModeHelper).onUserSwitched(eq(20)); @@ -15438,103 +15595,126 @@ 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()); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isFalse(); } - 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(); + @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); - return mService.findNotificationLocked( - mPkg, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getUserId()); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isTrue(); } - 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); - } + @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 PendingIntent createPendingIntent(String action) { - return PendingIntent.getActivity(mContext, 0, - new Intent(action).setPackage(mContext.getPackageName()), - PendingIntent.FLAG_MUTABLE); + assertThat(mService.checkDisqualifyingFeatures(mUserId, mUid, 0, null, r, false, false)) + .isTrue(); } - 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); - } + @Test + public void testClearUIJFromUninstallingPackage() throws Exception { + NotificationRecord r = + generateNotificationRecord(mTestNotificationChannel, 0, mUserId, "bar"); + mService.addNotification(r); - private boolean enqueueToast(String testPackage, ITransientNotification callback) - throws RemoteException { - return enqueueToast((INotificationManager) mService.mService, testPackage, new Binder(), - callback); - } + when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt())) + .thenThrow(PackageManager.NameNotFoundException.class); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); - 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); - } + mInternalService.cancelNotification(mPkg, mPkg, mUid, 0, r.getSbn().getTag(), + r.getSbn().getId(), mUserId); - private boolean enqueueTextToast(String testPackage, CharSequence text) throws RemoteException { - return enqueueTextToast(testPackage, text, /* isUiContext= */ true, DEFAULT_DISPLAY); + // no exception } - 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); + @Test + public void testPostFromMissingPackage_throws() throws Exception { + NotificationRecord r = + generateNotificationRecord(mTestNotificationChannel, 0, mUserId, "bar"); + + when(mPackageManagerClient.getPackageUidAsUser(anyString(), anyInt())) + .thenThrow(PackageManager.NameNotFoundException.class); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); + + try { + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, r.getSbn().getTag(), + r.getSbn().getId(), r.getSbn().getNotification(), + r.getSbn().getUserId()); + fail("Allowed to post a notification for an absent package"); + } catch (SecurityException e) { + // yay + } } - private void mockIsVisibleBackgroundUsersSupported(boolean supported) { - when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); + @Test + public void testGetEffectsSuppressor_noSuppressor() throws Exception { + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true); + assertThat(mBinderService.getEffectsSuppressor()).isNull(); } - private void mockIsUserVisible(int displayId, boolean visible) { - when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); + @Test + public void testGetEffectsSuppressor_suppressorSameApp() throws Exception { + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + mService.isSystemUid = false; + mService.isSystemAppId = false; + mBinderService.requestHintsFromListener(mock(INotificationListener.class), + HINT_HOST_DISABLE_EFFECTS); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(true); + assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component); } - private void mockDisplayAssignedToUser(int displayId) { - when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); + @Test + public void testGetEffectsSuppressor_suppressorDiffApp() throws Exception { + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + mService.isSystemUid = false; + mService.isSystemAppId = false; + mBinderService.requestHintsFromListener(mock(INotificationListener.class), + HINT_HOST_DISABLE_EFFECTS); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); + assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(null); } - 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 + public void testGetEffectsSuppressor_suppressorDiffAppSystemCaller() throws Exception { + when(mUmInternal.getProfileIds(anyInt(), anyBoolean())).thenReturn(new int[]{mUserId}); + when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener); + mService.isSystemUid = true; + mBinderService.requestHintsFromListener(mock(INotificationListener.class), + HINT_HOST_DISABLE_EFFECTS); + when(mPackageManagerInternal.isSameApp(anyString(), anyInt(), anyInt())).thenReturn(false); + assertThat(mBinderService.getEffectsSuppressor()).isEqualTo(mListener.component); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 5fdb3965be76..d1423fe71e50 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -3002,39 +3002,45 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode); } - private enum ModesApiFlag { - ENABLED(true, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER), - DISABLED(false, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI); + private enum ModesFlag { + MODES_UI(2, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER), + MODES_API(1, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_USER), + DISABLED(0, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI); - private final boolean mEnabled; + private final int mFlagsEnabled; @ConfigChangeOrigin private final int mOriginForUserActionInSystemUi; - ModesApiFlag(boolean enabled, @ConfigChangeOrigin int originForUserActionInSystemUi) { - this.mEnabled = enabled; + ModesFlag(int flagsEnabled, @ConfigChangeOrigin int originForUserActionInSystemUi) { + this.mFlagsEnabled = flagsEnabled; this.mOriginForUserActionInSystemUi = originForUserActionInSystemUi; } - void applyFlag(SetFlagsRule setFlagsRule) { - if (mEnabled) { + void applyFlags(SetFlagsRule setFlagsRule) { + if (mFlagsEnabled >= 1) { setFlagsRule.enableFlags(Flags.FLAG_MODES_API); } else { setFlagsRule.disableFlags(Flags.FLAG_MODES_API); } + if (mFlagsEnabled >= 2) { + setFlagsRule.enableFlags(Flags.FLAG_MODES_UI); + } else { + setFlagsRule.disableFlags(Flags.FLAG_MODES_UI); + } } } @Test - public void testZenModeEventLog_setManualZenMode(@TestParameter ModesApiFlag modesApiFlag) + public void testZenModeEventLog_setManualZenMode(@TestParameter ModesFlag modesFlag) throws IllegalArgumentException { - modesApiFlag.applyFlag(mSetFlagsRule); + modesFlag.applyFlags(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); // Turn zen mode on (to important_interruptions) // Need to additionally call the looper in order to finish the post-apply-config process mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID); + modesFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID); // Now turn zen mode off, but via a different package UID -- this should get registered as // "not an action by the user" because some other app is changing zen mode @@ -3062,7 +3068,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0)); assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); assertThat(mZenModeEventLogger.getFromSystemOrSystemUi(0)).isEqualTo( - modesApiFlag == ModesApiFlag.DISABLED); + modesFlag == ModesFlag.DISABLED); assertTrue(mZenModeEventLogger.getIsUserAction(0)); assertEquals(Process.SYSTEM_UID, mZenModeEventLogger.getPackageUid(0)); checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0)); @@ -3091,9 +3097,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_automaticRules(@TestParameter ModesApiFlag modesApiFlag) + public void testZenModeEventLog_automaticRules(@TestParameter ModesFlag modesFlag) throws IllegalArgumentException { - modesApiFlag.applyFlag(mSetFlagsRule); + modesFlag.applyFlags(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -3116,7 +3122,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Event 2: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); mZenModeHelper.updateAutomaticZenRule(id, zenRule, - modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID); + modesFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID); // Add a new system rule AutomaticZenRule systemRule = new AutomaticZenRule("systemRule", @@ -3134,7 +3140,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Event 4: "User" deletes the rule - mZenModeHelper.removeAutomaticZenRule(systemId, modesApiFlag.mOriginForUserActionInSystemUi, + mZenModeHelper.removeAutomaticZenRule(systemId, modesFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID); // In total, this represents 4 events @@ -3282,22 +3288,22 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_policyChanges(@TestParameter ModesApiFlag modesApiFlag) + public void testZenModeEventLog_policyChanges(@TestParameter ModesFlag modesFlag) throws IllegalArgumentException { - modesApiFlag.applyFlag(mSetFlagsRule); + modesFlag.applyFlags(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); // First just turn zen mode on mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - modesApiFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID); + modesFlag.mOriginForUserActionInSystemUi, "", null, Process.SYSTEM_UID); // Now change the policy slightly; want to confirm that this'll be reflected in the logs ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); newConfig.allowAlarms = true; newConfig.allowRepeatCallers = false; mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), - modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID); + modesFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID); // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode // is off. @@ -3308,7 +3314,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { newConfig.allowMessages = false; newConfig.allowRepeatCallers = true; mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), - modesApiFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID); + modesFlag.mOriginForUserActionInSystemUi, Process.SYSTEM_UID); // Total events: we only expect ones for turning on, changing policy, and turning off assertEquals(3, mZenModeEventLogger.numLoggedChanges()); @@ -3341,9 +3347,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_ruleCounts(@TestParameter ModesApiFlag modesApiFlag) + public void testZenModeEventLog_ruleCounts(@TestParameter ModesFlag modesFlag) throws IllegalArgumentException { - modesApiFlag.applyFlag(mSetFlagsRule); + modesFlag.applyFlags(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -3447,9 +3453,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenModeEventLog_noLogWithNoConfigChange( - @TestParameter ModesApiFlag modesApiFlag) throws IllegalArgumentException { + @TestParameter ModesFlag modesFlag) throws IllegalArgumentException { // If evaluateZenMode is called independently of a config change, don't log. - modesApiFlag.applyFlag(mSetFlagsRule); + modesFlag.applyFlags(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -3466,11 +3472,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testZenModeEventLog_reassignUid(@TestParameter ModesApiFlag modesApiFlag) + public void testZenModeEventLog_reassignUid(@TestParameter ModesFlag modesFlag) throws IllegalArgumentException { // Test that, only in specific cases, we reassign the calling UID to one associated with // the automatic rule owner. - modesApiFlag.applyFlag(mSetFlagsRule); + modesFlag.applyFlags(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -3496,7 +3502,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { manualRulePolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, - modesApiFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID); + modesFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID); // Turn on rule 1; call looks like it's from the system. Because setting a condition is // typically an automatic (non-user-initiated) action, expect the calling UID to be @@ -3515,7 +3521,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // from the system-provided one. zenRule.setEnabled(false); mZenModeHelper.updateAutomaticZenRule(id, zenRule, - modesApiFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID); + modesFlag.mOriginForUserActionInSystemUi, "", Process.SYSTEM_UID); // Add a manual rule. Any manual rule changes should not get calling uids reassigned. mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, @@ -3573,9 +3579,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenModeEventLog_channelsBypassingChanges( - @TestParameter ModesApiFlag modesApiFlag) { + @TestParameter ModesFlag modesFlag) { // Verify that the right thing happens when the canBypassDnd value changes. - modesApiFlag.applyFlag(mSetFlagsRule); + modesFlag.applyFlags(mSetFlagsRule); mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); @@ -3847,8 +3853,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDeviceDefault() { + public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDefault( + @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) { + modesFlag.applyFlags(mSetFlagsRule); setupZenConfig(); // When there's one automatic rule active and it doesn't specify a policy, test that the @@ -3869,7 +3876,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { // inspect the consolidated policy, which should match the device default settings. assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy)) - .isEqualTo(mZenModeHelper.getDefaultZenPolicy()); + .isEqualTo(modesFlag == ModesFlag.MODES_UI + ? mZenModeHelper.getDefaultZenPolicy() + : mZenModeHelper.mConfig.toZenPolicy()); } @Test @@ -3904,7 +3913,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // since this is the only active rule, the consolidated policy should match the custom - // policy for every field specified, and take default values for unspecified things + // policy for every field specified, and take default values (from device default or + // manual policy) for unspecified things assertTrue(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); // custom assertTrue(mZenModeHelper.mConsolidatedPolicy.allowMedia()); // custom assertFalse(mZenModeHelper.mConsolidatedPolicy.allowSystem()); // default @@ -3918,8 +3928,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDeviceDefault() { + public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDefault( + @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) { + modesFlag.applyFlags(mSetFlagsRule); setupZenConfig(); // when there's only one automatic rule active and it has a custom policy, make sure that's @@ -3948,11 +3959,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // since this is the only active rule, the consolidated policy should match the custom - // policy for every field specified, and take default values for unspecified things - assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue(); // default - assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // default + // policy for every field specified, and take default values (from either device default + // policy or manual rule) for unspecified things + assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isEqualTo( + modesFlag == ModesFlag.MODES_UI ? true : false); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isEqualTo( + modesFlag == ModesFlag.MODES_UI ? true : false); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue(); // custom - assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo( + modesFlag == ModesFlag.MODES_UI ? false : true); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse(); // custom assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()).isFalse(); // custom @@ -4022,8 +4037,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault() { + public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault( + @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) { + modesFlag.applyFlags(mSetFlagsRule); setupZenConfig(); // when there are two rules active, one inheriting the default policy and one setting its @@ -4071,16 +4087,19 @@ public class ZenModeHelperTest extends UiServiceTestCase { // now both rules should be on, and the consolidated policy should reflect the most // restrictive option of each of the two assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse(); // custom stricter - assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isEqualTo( + modesFlag == ModesFlag.MODES_UI ? true : false); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse(); // default stricter - assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo( + modesFlag == ModesFlag.MODES_UI ? false : true); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse(); // custom stricter assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()) .isFalse(); // custom stricter assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse(); // custom stricter - assertThat(mZenModeHelper.mConsolidatedPolicy.showPeeking()).isFalse(); // default stricter + assertThat(mZenModeHelper.mConsolidatedPolicy.showPeeking()).isEqualTo( + modesFlag == ModesFlag.MODES_UI ? false : true); // default } @Test @@ -4134,8 +4153,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - @EnableFlags(Flags.FLAG_MODES_API) - public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll() { + public void testUpdateConsolidatedPolicy_ignoresActiveRulesWithInterruptionFilterAll( + @TestParameter({"MODES_UI", "MODES_API"}) ModesFlag modesFlag) { + modesFlag.applyFlags(mSetFlagsRule); setupZenConfig(); // Rules with INTERRUPTION_FILTER_ALL are skipped when calculating consolidated policy. @@ -4172,10 +4192,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); // Consolidated Policy should be default + rule1. - assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isEqualTo( + modesFlag == ModesFlag.MODES_UI ? true : false); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // priority rule assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue(); // priority rule - assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isEqualTo( + modesFlag == ModesFlag.MODES_UI ? false : true); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isTrue(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default @@ -6251,7 +6273,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { } private void checkDndProtoMatchesDefaultZenConfig(DNDPolicyProto dndProto) { - if (!Flags.modesApi()) { + if (!Flags.modesUi()) { checkDndProtoMatchesSetupZenConfig(dndProto); return; } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java index 37e0818eb083..5787780cef46 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityOptionsTest.java @@ -24,6 +24,8 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; @@ -250,6 +252,7 @@ public class ActivityOptionsTest { case ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN: case ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN: case ActivityOptions.KEY_TRANSIENT_LAUNCH: + case ActivityOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED: case "android:activity.animationFinishedListener": // KEY_ANIMATION_FINISHED_LISTENER case "android:activity.animSpecs": // KEY_ANIM_SPECS @@ -319,7 +322,7 @@ public class ActivityOptionsTest { Log.e("ActivityOptionsTests", "Unknown key " + key + " is found. " + "Please review if the given bundle should be protected with permissions."); } - assertTrue(unknownKeys.isEmpty()); + assertThat(unknownKeys).isEmpty(); } public static class TrampolineActivity extends Activity { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java new file mode 100644 index 000000000000..12ab3e1bdf19 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRefresherTests.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE; +import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.servertransaction.RefreshCallbackItem; +import android.app.servertransaction.ResumeActivityItem; +import android.content.ComponentName; +import android.content.res.Configuration; +import android.os.Handler; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link ActivityRefresher}. + * + * <p>Build/Install/Run: + * atest WmTests:ActivityRefresherTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class ActivityRefresherTests extends WindowTestsBase { + private Handler mMockHandler; + private LetterboxConfiguration mLetterboxConfiguration; + + private ActivityRecord mActivity; + private ActivityRefresher mActivityRefresher; + + private ActivityRefresher.Evaluator mEvaluatorFalse = + (activity, newConfig, lastReportedConfig) -> false; + + private ActivityRefresher.Evaluator mEvaluatorTrue = + (activity, newConfig, lastReportedConfig) -> true; + + private final Configuration mNewConfig = new Configuration(); + private final Configuration mOldConfig = new Configuration(); + + @Before + public void setUp() throws Exception { + mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration; + spyOn(mLetterboxConfiguration); + when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled()) + .thenReturn(true); + when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + .thenReturn(true); + when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + .thenReturn(true); + + mMockHandler = mock(Handler.class); + when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }); + + mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler); + } + + @Test + public void testShouldRefreshActivity_refreshDisabled() throws Exception { + when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()) + .thenReturn(false); + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ false); + } + + @Test + public void testShouldRefreshActivity_refreshDisabledForActivity() throws Exception { + configureActivityAndDisplay(); + when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()) + .thenReturn(false); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ false); + } + + @Test + public void testShouldRefreshActivity_noRefreshTriggerers() throws Exception { + configureActivityAndDisplay(); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ false); + } + + @Test + public void testShouldRefreshActivity_refreshTriggerersReturnFalse() throws Exception { + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorFalse); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ false); + } + + @Test + public void testShouldRefreshActivity_anyRefreshTriggerersReturnTrue() throws Exception { + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorFalse); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested= */ true); + } + + @Test + public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() + throws Exception { + mActivityRefresher.addEvaluator(mEvaluatorTrue); + when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled()) + .thenReturn(false); + configureActivityAndDisplay(); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + } + + @Test + public void testOnActivityConfigurationChanging_cycleThroughPauseEnabledForApp() + throws Exception { + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + doReturn(true).when(mActivity.mLetterboxUiController) + .shouldRefreshActivityViaPauseForCameraCompat(); + + mActivityRefresher.onActivityConfigurationChanging(mActivity, mNewConfig, mOldConfig); + + assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); + } + + @Test + public void testOnActivityRefreshed_setIsRefreshRequestedToFalse() throws Exception { + configureActivityAndDisplay(); + mActivityRefresher.addEvaluator(mEvaluatorTrue); + doReturn(true).when(mActivity.mLetterboxUiController) + .shouldRefreshActivityViaPauseForCameraCompat(); + + mActivityRefresher.onActivityRefreshed(mActivity); + + assertActivityRefreshRequested(false); + } + + private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception { + assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true); + } + + private void assertActivityRefreshRequested(boolean refreshRequested, + boolean cycleThroughStop) throws Exception { + verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0)) + .setIsRefreshRequested(true); + + final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token, + cycleThroughStop ? ON_STOP : ON_PAUSE); + final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(mActivity.token, + /* isForward */ false, /* shouldSendCompatFakeFocus */ false); + + verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0)) + .scheduleTransactionAndLifecycleItems(mActivity.app.getThread(), + refreshCallbackItem, resumeActivityItem); + } + + private void configureActivityAndDisplay() { + mActivity = new TaskBuilder(mSupervisor) + .setCreateActivity(true) + .setDisplay(mDisplayContent) + // Set the component to be that of the test class in order to enable compat changes + .setComponent(ComponentName.createRelative(mContext, + ActivityRefresherTests.class.getName())) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build() + .getTopMostActivity(); + + spyOn(mActivity.mLetterboxUiController); + doReturn(true).when( + mActivity.mLetterboxUiController).shouldRefreshActivityForCameraCompat(); + + doReturn(true).when(mActivity).inFreeformWindowingMode(); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index 10eae577f706..13550923cf3d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -46,7 +46,6 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; -import static android.server.wm.ActivityManagerTestBase.isTablet; import static com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; @@ -76,7 +75,6 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; @@ -124,7 +122,6 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.wm.BackgroundActivityStartController.BalVerdict; import com.android.server.wm.LaunchParamsController.LaunchParamsModifier; import com.android.server.wm.utils.MockTracker; -import com.android.window.flags.Flags; import org.junit.After; import org.junit.Before; @@ -1298,12 +1295,6 @@ public class ActivityStarterTests extends WindowTestsBase { */ @Test public void testDeliverIntentToTopActivityOfNonTopDisplay() { - // TODO(b/330152508): Remove check once legacy multi-display behaviour can coexist with - // desktop windowing mode - // Ignore test if desktop windowing is enabled on tablets as legacy multi-display - // behaviour will not be respected - assumeFalse(Flags.enableDesktopWindowingMode() && isTablet()); - final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */); diff --git a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java index 695faa525dd6..39a2259cf77f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackgroundActivityStartControllerTests.java @@ -19,6 +19,8 @@ package com.android.server.wm; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW; +import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK; +import static com.android.window.flags.Flags.balImprovedMetrics; import static com.google.common.truth.Truth.assertThat; @@ -145,6 +147,16 @@ public class BackgroundActivityStartControllerTests { } @Override + boolean shouldLogStats(BalVerdict finalVerdict, BalState state) { + return true; + } + + @Override + boolean shouldLogIntentActivity(BalVerdict finalVerdict, BalState state) { + return true; + } + + @Override BalVerdict checkBackgroundActivityStartAllowedByCaller(BalState state) { return mCallerVerdict.orElseGet( () -> super.checkBackgroundActivityStartAllowedByCaller(state)); @@ -238,7 +250,12 @@ public class BackgroundActivityStartControllerTests { // assertions assertThat(verdict.getCode()).isEqualTo(BackgroundActivityStartController.BAL_BLOCK); - assertThat(mBalAllowedLogs).isEmpty(); // not allowed + if (balImprovedMetrics()) { + assertThat(mBalAllowedLogs).containsExactly( + new BalAllowedLog("package.app3/someClass", BAL_BLOCK)); + } else { + assertThat(mBalAllowedLogs).isEmpty(); // not allowed + } } // Tests for BackgroundActivityStartController.checkBackgroundActivityStart @@ -268,7 +285,12 @@ public class BackgroundActivityStartControllerTests { // assertions assertThat(verdict).isEqualTo(BalVerdict.BLOCK); - assertThat(mBalAllowedLogs).isEmpty(); // not allowed + if (balImprovedMetrics()) { + assertThat(mBalAllowedLogs).containsExactly( + new BalAllowedLog("package.app3/someClass", BAL_BLOCK)); + } else { + assertThat(mBalAllowedLogs).isEmpty(); // not allowed + } } @Test @@ -298,7 +320,12 @@ public class BackgroundActivityStartControllerTests { // assertions assertThat(verdict).isEqualTo(callerVerdict); - assertThat(mBalAllowedLogs).isEmpty(); // non-critical exception + if (balImprovedMetrics()) { + assertThat(mBalAllowedLogs).containsExactly( + new BalAllowedLog("package.app3/someClass", callerVerdict.getCode())); + } else { + assertThat(mBalAllowedLogs).isEmpty(); // non-critical exception + } } @Test @@ -362,7 +389,13 @@ public class BackgroundActivityStartControllerTests { // assertions assertThat(verdict).isEqualTo(callerVerdict); - assertThat(mBalAllowedLogs).containsExactly(new BalAllowedLog("", callerVerdict.getCode())); + if (balImprovedMetrics()) { + assertThat(mBalAllowedLogs).containsExactly( + new BalAllowedLog("package.app3/someClass", callerVerdict.getCode())); + } else { + assertThat(mBalAllowedLogs).containsExactly( + new BalAllowedLog("", callerVerdict.getCode())); + } } @Test @@ -398,7 +431,12 @@ public class BackgroundActivityStartControllerTests { // assertions assertThat(verdict).isEqualTo(BalVerdict.BLOCK); - assertThat(mBalAllowedLogs).isEmpty(); + if (balImprovedMetrics()) { + assertThat(mBalAllowedLogs).containsExactly( + new BalAllowedLog("package.app3/someClass", BAL_BLOCK)); + } else { + assertThat(mBalAllowedLogs).isEmpty(); + } } @Test @@ -430,7 +468,12 @@ public class BackgroundActivityStartControllerTests { // assertions assertThat(verdict).isEqualTo(callerVerdict); - assertThat(mBalAllowedLogs).isEmpty(); + if (balImprovedMetrics()) { + assertThat(mBalAllowedLogs).containsExactly( + new BalAllowedLog("package.app3/someClass", callerVerdict.getCode())); + } else { + assertThat(mBalAllowedLogs).isEmpty(); + } } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java index f92387cdf5c9..a268aa912183 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java @@ -79,19 +79,19 @@ public class DesktopModeLaunchParamsModifierTests extends WindowTestsBase { @Test @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void testReturnsContinueIfDesktopWindowingIsDisabled() { + public void testReturnsSkipIfDesktopWindowingIsDisabled() { setupDesktopModeLaunchParamsModifier(); - assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate()); + assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate()); } @Test @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE) - public void testReturnsContinueIfDesktopWindowingIsEnabledOnUnsupportedDevice() { + public void testReturnsSkipIfDesktopWindowingIsEnabledOnUnsupportedDevice() { setupDesktopModeLaunchParamsModifier(/*isDesktopModeSupported=*/ false, /*enforceDeviceRestrictions=*/ true); - assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(null).calculate()); + assertEquals(RESULT_SKIP, new CalculateRequestBuilder().setTask(null).calculate()); } @Test 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 27d9d1356d7f..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,13 +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; @@ -129,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; @@ -149,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; @@ -176,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() { @@ -512,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); @@ -2813,6 +2766,41 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.getKeepClearAreas()); } + @Test + public void testHasAccessConsidersUserVisibilityForBackgroundVisibleUsers() { + doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled()); + final int appId = 1234; + final int userId1 = 11; + final int userId2 = 12; + final int uid1 = UserHandle.getUid(userId1, appId); + final int uid2 = UserHandle.getUid(userId2, appId); + final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); + final DisplayContent dc = createNewDisplay(displayInfo); + int displayId = dc.getDisplayId(); + doReturn(true).when(mWm.mUmInternal).isUserVisible(userId1, displayId); + doReturn(false).when(mWm.mUmInternal).isUserVisible(userId2, displayId); + + assertTrue(dc.hasAccess(uid1)); + assertFalse(dc.hasAccess(uid2)); + } + + @Test + public void testHasAccessIgnoresUserVisibilityForPrivateDisplay() { + doReturn(true).when(() -> UserManager.isVisibleBackgroundUsersEnabled()); + final int appId = 1234; + final int userId2 = 12; + final int uid2 = UserHandle.getUid(userId2, appId); + final DisplayInfo displayInfo = new DisplayInfo(mDisplayInfo); + displayInfo.flags = FLAG_PRIVATE; + displayInfo.ownerUid = uid2; + final DisplayContent dc = createNewDisplay(displayInfo); + int displayId = dc.getDisplayId(); + + assertTrue(dc.hasAccess(uid2)); + + verify(mWm.mUmInternal, never()).isUserVisible(userId2, displayId); + } + private void removeRootTaskTests(Runnable runnable) { final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN, @@ -2922,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/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java index 262ba8bb32f3..c76acd7e1d6b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java @@ -91,6 +91,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { private CameraManager mMockCameraManager; private Handler mMockHandler; private LetterboxConfiguration mLetterboxConfiguration; + private ActivityRefresher mActivityRefresher; private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; private CameraManager.AvailabilityCallback mCameraAvailabilityCallback; @@ -132,8 +133,9 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { }); CameraStateMonitor cameraStateMonitor = new CameraStateMonitor(mDisplayContent, mMockHandler); - mDisplayRotationCompatPolicy = - new DisplayRotationCompatPolicy(mDisplayContent, mMockHandler, cameraStateMonitor); + mActivityRefresher = new ActivityRefresher(mDisplayContent.mWmService, mMockHandler); + mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(mDisplayContent, + cameraStateMonitor, mActivityRefresher); // Do not show the real toast. spyOn(mDisplayRotationCompatPolicy); @@ -606,7 +608,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { private void assertActivityRefreshRequested(boolean refreshRequested, boolean cycleThroughStop) throws Exception { verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0)) - .setIsRefreshAfterRotationRequested(true); + .setIsRefreshRequested(true); final RefreshCallbackItem refreshCallbackItem = RefreshCallbackItem.obtain(mActivity.token, cycleThroughStop ? ON_STOP : ON_PAUSE); @@ -628,7 +630,7 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { private void callOnActivityConfigurationChanging( ActivityRecord activity, boolean isDisplayRotationChanging) { - mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity, + mActivityRefresher.onActivityConfigurationChanging(activity, /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0), /* newConfig */ createConfigurationWithDisplayRotation( isDisplayRotationChanging ? ROTATION_90 : ROTATION_0)); diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java index 7380aecbf47b..d8d5729700ca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java @@ -65,7 +65,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { performSurfacePlacementAndWaitForWindowAnimator(); mImeProvider.scheduleShowImePostLayout(appWin, ImeTracker.Token.empty()); - assertTrue(mImeProvider.isReadyToShowIme()); + assertTrue(mImeProvider.isScheduledAndReadyToShowIme()); } /** @@ -84,13 +84,13 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // Schedule (without triggering) after everything is ready. mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); - assertTrue(mImeProvider.isReadyToShowIme()); + assertTrue(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); // Manually trigger the show. - mImeProvider.checkShowImePostLayout(); - // No longer ready as it was already shown. - assertFalse(mImeProvider.isReadyToShowIme()); + mImeProvider.checkAndStartShowImePostLayout(); + // No longer scheduled as it was already shown. + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertTrue(mImeProvider.isImeShowing()); } @@ -104,7 +104,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // Schedule before anything is ready. mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); - assertFalse(mImeProvider.isReadyToShowIme()); + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime"); @@ -115,8 +115,8 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mDisplayContent.updateImeInputAndControlTarget(target); // Performing surface placement picks up the show scheduled above. performSurfacePlacementAndWaitForWindowAnimator(); - // No longer ready as it was already shown. - assertFalse(mImeProvider.isReadyToShowIme()); + // No longer scheduled as it was already shown. + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertTrue(mImeProvider.isImeShowing()); } @@ -137,19 +137,19 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // Schedule before starting the afterPrepareSurfacesRunnable. mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); - assertFalse(mImeProvider.isReadyToShowIme()); + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); // This tries to pick up the show scheduled above, but must fail as the // afterPrepareSurfacesRunnable was not started yet. mDisplayContent.applySurfaceChangesTransaction(); - assertFalse(mImeProvider.isReadyToShowIme()); + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); // Starting the afterPrepareSurfacesRunnable picks up the show scheduled above. mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); - // No longer ready as it was already shown. - assertFalse(mImeProvider.isReadyToShowIme()); + // No longer scheduled as it was already shown. + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertTrue(mImeProvider.isImeShowing()); } @@ -169,7 +169,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // Schedule before surface placement. mImeProvider.scheduleShowImePostLayout(target, ImeTracker.Token.empty()); - assertFalse(mImeProvider.isReadyToShowIme()); + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertFalse(mImeProvider.isImeShowing()); // Performing surface placement picks up the show scheduled above, and succeeds. @@ -177,8 +177,8 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { // applySurfaceChangesTransaction. Both of them try to trigger the show, // but only the second one can succeed, as it comes after onPostLayout. performSurfacePlacementAndWaitForWindowAnimator(); - // No longer ready as it was already shown. - assertFalse(mImeProvider.isReadyToShowIme()); + // No longer scheduled as it was already shown. + assertFalse(mImeProvider.isScheduledAndReadyToShowIme()); assertTrue(mImeProvider.isImeShowing()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java index 0e1a1af9bc48..c69faede7580 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java @@ -353,6 +353,17 @@ public class InsetsStateControllerTest extends WindowTestsBase { } @Test + public void testControlTargetChangedWhileProviderHasNoWindow() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final InsetsSourceProvider provider = getController().getOrCreateSourceProvider( + ID_STATUS_BAR, statusBars()); + getController().onBarControlTargetChanged(app, null, null, null); + assertNull(getController().getControlsForDispatch(app)); + provider.setWindowContainer(createWindow(null, TYPE_APPLICATION, "statusBar"), null, null); + assertNotNull(getController().getControlsForDispatch(app)); + } + + @Test public void testTransientVisibilityOfFixedRotationState() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 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 b74da1a888cd..c7f502045ac8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -85,6 +85,8 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; import android.content.res.Resources; import android.graphics.Rect; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.view.InsetsSource; import android.view.InsetsState; @@ -96,6 +98,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.window.flags.Flags; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @@ -905,6 +908,24 @@ public class LetterboxUiControllerTest extends WindowTestsBase { } @Test + public void testOverrideOrientationIfNeeded_fullscreenOverride_cameraActivity_unchanged() { + doReturn(true).when(mLetterboxConfiguration).isCameraCompatTreatmentEnabled(); + doReturn(true).when(mLetterboxConfiguration) + .isCameraCompatTreatmentEnabledAtBuildTime(); + + // Recreate DisplayContent with DisplayRotationCompatPolicy + mActivity = setUpActivityWithComponent(); + mController = new LetterboxUiController(mWm, mActivity); + spyOn(mDisplayContent.mDisplayRotationCompatPolicy); + + doReturn(false).when(mDisplayContent.mDisplayRotationCompatPolicy) + .isCameraActive(mActivity, /* mustBeFullscreen= */ true); + + assertEquals(SCREEN_ORIENTATION_PORTRAIT, mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT)); + } + + @Test public void testOverrideOrientationIfNeeded_respectOrientationRequestOverUserFullScreen() { spyOn(mController); doReturn(true).when(mController).shouldApplyUserFullscreenOverride(); @@ -1528,6 +1549,104 @@ public class LetterboxUiControllerTest extends WindowTestsBase { mActivity.getParent().getConfiguration()), /* delta */ 0.01); } + @Test + public void testIsVerticalThinLetterboxed() { + // Vertical thin letterbox disabled + doReturn(-1).when(mActivity.mWmService.mLetterboxConfiguration) + .getThinLetterboxHeightPx(); + assertFalse(mController.isVerticalThinLetterboxed()); + // Define a Task 100x100 + final Task task = mock(Task.class); + doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds(); + doReturn(10).when(mActivity.mWmService.mLetterboxConfiguration) + .getThinLetterboxHeightPx(); + + // Vertical thin letterbox disabled without Task + doReturn(null).when(mActivity).getTask(); + assertFalse(mController.isVerticalThinLetterboxed()); + // Assign a Task for the Activity + doReturn(task).when(mActivity).getTask(); + + // (task.width() - act.width()) / 2 = 5 < 10 + doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds(); + assertTrue(mController.isVerticalThinLetterboxed()); + + // (task.width() - act.width()) / 2 = 10 = 10 + doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds(); + assertTrue(mController.isVerticalThinLetterboxed()); + + // (task.width() - act.width()) / 2 = 11 > 10 + doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds(); + assertFalse(mController.isVerticalThinLetterboxed()); + } + + @Test + public void testIsHorizontalThinLetterboxed() { + // Horizontal thin letterbox disabled + doReturn(-1).when(mActivity.mWmService.mLetterboxConfiguration) + .getThinLetterboxWidthPx(); + assertFalse(mController.isHorizontalThinLetterboxed()); + // Define a Task 100x100 + final Task task = mock(Task.class); + doReturn(new Rect(0, 0, 100, 100)).when(task).getBounds(); + doReturn(10).when(mActivity.mWmService.mLetterboxConfiguration) + .getThinLetterboxWidthPx(); + + // Vertical thin letterbox disabled without Task + doReturn(null).when(mActivity).getTask(); + assertFalse(mController.isHorizontalThinLetterboxed()); + // Assign a Task for the Activity + doReturn(task).when(mActivity).getTask(); + + // (task.height() - act.height()) / 2 = 5 < 10 + doReturn(new Rect(5, 5, 95, 95)).when(mActivity).getBounds(); + assertTrue(mController.isHorizontalThinLetterboxed()); + + // (task.height() - act.height()) / 2 = 10 = 10 + doReturn(new Rect(10, 10, 90, 90)).when(mActivity).getBounds(); + assertTrue(mController.isHorizontalThinLetterboxed()); + + // (task.height() - act.height()) / 2 = 11 > 10 + doReturn(new Rect(11, 11, 89, 89)).when(mActivity).getBounds(); + assertFalse(mController.isHorizontalThinLetterboxed()); + } + + @Test + @EnableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY) + public void testAllowReachabilityForThinLetterboxWithFlagEnabled() { + spyOn(mController); + doReturn(true).when(mController).isVerticalThinLetterboxed(); + assertFalse(mController.allowVerticalReachabilityForThinLetterbox()); + doReturn(true).when(mController).isHorizontalThinLetterboxed(); + assertFalse(mController.allowHorizontalReachabilityForThinLetterbox()); + + doReturn(false).when(mController).isVerticalThinLetterboxed(); + assertTrue(mController.allowVerticalReachabilityForThinLetterbox()); + doReturn(false).when(mController).isHorizontalThinLetterboxed(); + assertTrue(mController.allowHorizontalReachabilityForThinLetterbox()); + } + + @Test + @DisableFlags(Flags.FLAG_DISABLE_THIN_LETTERBOXING_POLICY) + public void testAllowReachabilityForThinLetterboxWithFlagDisabled() { + spyOn(mController); + doReturn(true).when(mController).isVerticalThinLetterboxed(); + assertTrue(mController.allowVerticalReachabilityForThinLetterbox()); + doReturn(true).when(mController).isHorizontalThinLetterboxed(); + assertTrue(mController.allowHorizontalReachabilityForThinLetterbox()); + + doReturn(false).when(mController).isVerticalThinLetterboxed(); + assertTrue(mController.allowVerticalReachabilityForThinLetterbox()); + doReturn(false).when(mController).isHorizontalThinLetterboxed(); + 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/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java index 75b84d1c8a64..6ec1429200e6 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java @@ -1373,26 +1373,6 @@ public class RecentTasksTest extends WindowTestsBase { assertTrue(info.supportsMultiWindow); } - @Test - public void testRemoveCompatibleRecentTask() { - final Task task1 = createTaskBuilder(".Task").setWindowingMode( - WINDOWING_MODE_FULLSCREEN).build(); - mRecentTasks.add(task1); - final Task task2 = createTaskBuilder(".Task").setWindowingMode( - WINDOWING_MODE_MULTI_WINDOW).build(); - mRecentTasks.add(task2); - assertEquals(2, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, - true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size()); - - // Set windowing mode and ensure the same fullscreen task that created earlier is removed. - task2.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - mRecentTasks.removeCompatibleRecentTask(task2); - assertEquals(1, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, - true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().size()); - assertEquals(task2.mTaskId, mRecentTasks.getRecentTasks(MAX_VALUE, 0 /* flags */, - true /* getTasksAllowed */, TEST_USER_0_ID, 0).getList().get(0).taskId); - } - private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) { HardwareBuffer buffer = null; if (bufferSize != null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 8677738f3edc..6b605ec6d0c0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -3646,11 +3646,27 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testIsReachabilityEnabled_thisLetterbox_false() { + // Case when the reachability would be enabled otherwise + setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); + mActivity.getWindowConfiguration().setBounds(null); + + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ false); + + assertFalse(mActivity.mLetterboxUiController.isVerticalReachabilityEnabled()); + assertFalse(mActivity.mLetterboxUiController.isHorizontalReachabilityEnabled()); + } + + @Test public void testIsHorizontalReachabilityEnabled_splitScreen_false() { mAtm.mDevEnableNonResizableMultiWindow = true; setUpDisplaySizeWithApp(2800, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); @@ -3673,6 +3689,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mActivity.getDisplayContent()); @@ -3694,6 +3711,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1000, 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // Unresizable landscape-only activity. prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE); @@ -3715,6 +3733,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -3731,6 +3750,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(/* dw */ 2800, /* dh */ 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -3747,6 +3767,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait display setUpDisplaySizeWithApp(1400, 1600); mActivity.mWmService.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // 16:9f unresizable portrait app prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, @@ -3760,6 +3781,7 @@ public class SizeCompatTests extends WindowTestsBase { // Landscape display setUpDisplaySizeWithApp(1600, 1500); mActivity.mWmService.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // 16:9f unresizable landscape app prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, @@ -3773,6 +3795,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(2800, 1000); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // Unresizable portrait-only activity. prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT); @@ -3794,6 +3817,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(1800, 2200); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // Unresizable portrait-only activity. prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -3815,6 +3839,7 @@ public class SizeCompatTests extends WindowTestsBase { setUpDisplaySizeWithApp(2200, 1800); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true); // Unresizable landscape-only activity. prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); @@ -5011,6 +5036,14 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(activity.shouldSendCompatFakeFocus()); } + private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) { + spyOn(mActivity.mLetterboxUiController); + doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController) + .allowVerticalReachabilityForThinLetterbox(); + doReturn(thinLetterboxAllowed).when(mActivity.mLetterboxUiController) + .allowHorizontalReachabilityForThinLetterbox(); + } + private int getExpectedSplitSize(int dimensionToSplit) { int dividerWindowWidth = mActivity.mWmService.mContext.getResources().getDimensionPixelSize( 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 83e4151235ea..d9fd312423d1 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -487,6 +487,16 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { // Flush EVENT_APPEARED. mController.dispatchPendingEvents(); + // Even if the activity is not launched in an organized TaskFragment, it is still considered + // as the remote activity to the organizer process. Because when the task becomes visible, + // the organizer process needs to be interactive (unfrozen) to receive TaskFragment events. + activity.setVisibleRequested(true); + activity.setState(ActivityRecord.State.RESUMED, "test"); + assertTrue(organizerProc.hasVisibleActivities()); + activity.setVisibleRequested(false); + activity.setState(ActivityRecord.State.STOPPED, "test"); + assertFalse(organizerProc.hasVisibleActivities()); + // Make sure the activity belongs to the same app, but it is in a different pid. activity.info.applicationInfo.uid = uid; doReturn(pid + 1).when(activity).getPid(); @@ -1884,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(); @@ -1903,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/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index a90a158e0c2a..d57a7e61ad63 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.Manifest.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE; import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; @@ -961,6 +962,22 @@ public class TaskFragmentTest extends WindowTestsBase { assertEquals(appLeftTop, task.getDisplayContent().mFocusedApp); } + @Test + public void testShouldBeVisible_invisibleForEmptyTaskFragment() { + final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build(); + final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .build(); + + // Empty taskFragment should be invisible + assertFalse(taskFragment.shouldBeVisible(null)); + + // Should be invisible even if it is ACTIVITY_TYPE_HOME. + when(taskFragment.getActivityType()).thenReturn(ACTIVITY_TYPE_HOME); + assertFalse(taskFragment.shouldBeVisible(null)); + } + private WindowState createAppWindow(ActivityRecord app, String name) { final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name, 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow()); 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/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 328878818b86..80c066debf1a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1236,7 +1236,9 @@ public class TransitionTests extends WindowTestsBase { final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, "statusBar"); makeWindowVisible(statusBar); mDisplayContent.getDisplayPolicy().addWindowLw(statusBar, statusBar.mAttrs); - final WindowState navBar = createWindow(null, TYPE_NAVIGATION_BAR, "navBar"); + final WindowState navBar = createNavBarWithProvidedInsets(mDisplayContent); + final InsetsSourceProvider navBarInsetsProvider = navBar.getControllableInsetProvider(); + assertNotNull(navBarInsetsProvider); final ActivityRecord app = createActivityRecord(mDisplayContent); final Transition transition = app.mTransitionController.createTransition(TRANSIT_OPEN); app.mTransitionController.requestStartTransition(transition, app.getTask(), @@ -1282,11 +1284,15 @@ public class TransitionTests extends WindowTestsBase { onRotationTransactionReady(player, mWm.mTransactionFactory.get()).onTransactionCommitted(); assertEquals(ROTATION_ANIMATION_SEAMLESS, player.mLastReady.getChange( mDisplayContent.mRemoteToken.toWindowContainerToken()).getRotationAnimation()); + spyOn(navBarInsetsProvider); player.finish(); // The controller should be cleared if the target windows are drawn. statusBar.finishDrawing(mWm.mTransactionFactory.get(), Integer.MAX_VALUE); assertNull(mDisplayContent.getAsyncRotationController()); + // The shouldFreezeInsetsPosition for navBar was true, so its insets position should be + // updated if the transition is done. + verify(navBarInsetsProvider).updateInsetsControlPosition(navBar); } private static void assertShouldFreezeInsetsPosition(AsyncRotationController controller, diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 5b1a18da3173..9b48cb9d328c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -314,6 +314,18 @@ public class WallpaperControllerTests extends WindowTestsBase { // Wallpaper is invisible because the lowest show-when-locked activity is opaque. assertNull(wallpaperController.getWallpaperTarget()); + // Only transient-launch transition will make notification shade as last resort target. + // This verifies that regular transition won't choose invisible keyguard as the target. + final WindowState keyguard = createWindow(null /* parent */, + WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE, "keyguard"); + keyguard.mAttrs.flags |= FLAG_SHOW_WALLPAPER; + registerTestTransitionPlayer(); + final Transition transition = wallpaperWindow.mTransitionController.createTransition( + WindowManager.TRANSIT_CHANGE); + transition.collect(keyguard); + wallpaperController.adjustWallpaperWindows(); + assertNull(wallpaperController.getWallpaperTarget()); + // A show-when-locked wallpaper is used for lockscreen. So the top wallpaper should // be the one that is not show-when-locked. final WindowState wallpaperWindow2 = createWallpaperWindow(mDisplayContent); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index a78fc10bd893..7e6301fda872 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -21,13 +21,15 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.permission.flags.Flags.FLAG_SENSITIVE_CONTENT_IMPROVEMENTS; +import static android.permission.flags.Flags.FLAG_SENSITIVE_CONTENT_RECENTS_SCREENSHOT_BUGFIX; import static android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_OWN_FOCUS; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; import static android.view.WindowManager.LayoutParams.FLAG_SECURE; -import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_TRACING; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY; import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; @@ -1020,6 +1022,35 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test + @RequiresFlagsEnabled( + {FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION, FLAG_SENSITIVE_CONTENT_IMPROVEMENTS, + FLAG_SENSITIVE_CONTENT_RECENTS_SCREENSHOT_BUGFIX}) + public void addBlockScreenCaptureForApps_appNotInForeground_invalidateSnapshot() { + spyOn(mWm.mTaskSnapshotController); + + // createAppWindow uses package name of "test" and uid of "0" + String testPackage = "test"; + int ownerId1 = 0; + + final Task task = createTask(mDisplayContent); + final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "appWindow"); + mWm.mWindowMap.put(win.mClient.asBinder(), win); + final ActivityRecord activity = win.mActivityRecord; + activity.setVisibleRequested(false); + activity.setVisible(false); + win.setHasSurface(false); + + PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1); + ArraySet<PackageInfo> blockedPackages = new ArraySet(); + blockedPackages.add(blockedPackage); + + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + wmInternal.addBlockScreenCaptureForApps(blockedPackages); + + verify(mWm.mTaskSnapshotController).removeAndDeleteSnapshot(anyInt(), eq(ownerId1)); + } + + @Test public void clearBlockedApps_clearsCache() { String testPackage = "test"; int ownerId1 = 20; @@ -1192,20 +1223,20 @@ public class WindowManagerServiceTests extends WindowTestsBase { final InputChannel inputChannel = new InputChannel(); mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */, - INPUT_FEATURE_SENSITIVE_FOR_TRACING, TYPE_APPLICATION, null /* windowToken */, + INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, TYPE_APPLICATION, null /* windowToken */, inputTransferToken, "TestInputChannel", inputChannel); verify(mTransaction).setInputWindowInfo( eq(surfaceControl), - argThat(h -> (h.inputConfig & InputConfig.SENSITIVE_FOR_TRACING) == 0)); + argThat(h -> (h.inputConfig & InputConfig.SENSITIVE_FOR_PRIVACY) == 0)); mWm.updateInputChannel(inputChannel.getToken(), DEFAULT_DISPLAY, surfaceControl, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, - INPUT_FEATURE_SENSITIVE_FOR_TRACING, + INPUT_FEATURE_SENSITIVE_FOR_PRIVACY, null /* region */); verify(mTransaction).setInputWindowInfo( eq(surfaceControl), - argThat(h -> (h.inputConfig & InputConfig.SENSITIVE_FOR_TRACING) != 0)); + argThat(h -> (h.inputConfig & InputConfig.SENSITIVE_FOR_PRIVACY) != 0)); } @RequiresFlagsDisabled(Flags.FLAG_ALWAYS_DRAW_MAGNIFICATION_FULLSCREEN_BORDER) 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/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 9d14290bdd8a..2e93cba80386 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -654,7 +654,10 @@ public class UsageStatsService extends SystemService implements } } else if (Intent.ACTION_USER_STARTED.equals(action)) { if (userId >= 0) { - mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget(); + if (!Flags.disableIdleCheck() || userId > 0) { + // Don't check idle state for USER_SYSTEM during the boot up. + mHandler.obtainMessage(MSG_USER_STARTED, userId, 0).sendToTarget(); + } } } } @@ -2013,6 +2016,8 @@ public class UsageStatsService extends SystemService implements + ": " + Flags.useParceledList()); pw.println(" " + Flags.FLAG_FILTER_BASED_EVENT_QUERY_API + ": " + Flags.filterBasedEventQueryApi()); + pw.println(" " + Flags.FLAG_DISABLE_IDLE_CHECK + + ": " + Flags.disableIdleCheck()); final int[] userIds; synchronized (mLock) { diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 9acda5f249d2..e8bac66a724a 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -37,6 +37,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -62,6 +63,7 @@ import android.os.Environment; import android.os.FileUtils; import android.os.Handler; import android.os.HwBinder; +import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; @@ -89,6 +91,7 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.usb.flags.Flags; import com.android.server.usb.hal.gadget.UsbGadgetHal; import com.android.server.usb.hal.gadget.UsbGadgetHalInstance; import com.android.server.utils.EventLogger; @@ -586,6 +589,22 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser */ protected static final String USB_PERSISTENT_CONFIG_PROPERTY = "persist.sys.usb.config"; + protected static final String MTP_PACKAGE_NAME = "com.android.mtp"; + protected static final String MTP_SERVICE_CLASS_NAME = "com.android.mtp.MtpService"; + + private boolean mIsMtpServiceBound = false; + + /** + * {@link ServiceConnection} for {@link MtpService}. + */ + private ServiceConnection mMtpServiceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) {} + + @Override + public void onServiceDisconnected(ComponentName arg0) {} + }; + UsbHandler(Looper looper, Context context, UsbDeviceManager deviceManager, UsbAlsaManager alsaManager, UsbPermissionManager permissionManager) { super(looper); @@ -915,6 +934,7 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private void updateUsbFunctions() { updateMidiFunction(); + updateMtpFunction(); } private void updateMidiFunction() { @@ -941,6 +961,67 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser mMidiEnabled && mConfigured, mMidiCard, mMidiDevice); } + /** + * Bind to MtpService when MTP or PTP is enabled. This is done to prevent activity manager + * from freezing the corresponding process. + */ + private void updateMtpFunction() { + if (!Flags.enableBindToMtpService()) { + return; + } + + boolean mtpEnabled = ((mCurrentFunctions & UsbManager.FUNCTION_MTP) != 0); + boolean ptpEnabled = ((mCurrentFunctions & UsbManager.FUNCTION_PTP) != 0); + + if (DEBUG) { + Slog.d(TAG, "updateMtpFunction " + + ", mtpEnabled: " + mtpEnabled + + ", ptpEnabled: " + ptpEnabled + + ", mIsMtpServiceBound: " + mIsMtpServiceBound + ); + } + + if (mConfigured && (mtpEnabled || ptpEnabled)) { + bindToMtpService(); + } else if (mIsMtpServiceBound) { + unbindMtpService(); + } + } + + private void bindToMtpService() { + Intent intent = new Intent(); + intent.setComponent(new ComponentName(MTP_PACKAGE_NAME, MTP_SERVICE_CLASS_NAME)); + + if (DEBUG) Slog.d(TAG, "Binding to MtpService"); + + try { + mIsMtpServiceBound = mContext.bindServiceAsUser( + intent, + mMtpServiceConnection, + Context.BIND_AUTO_CREATE, + UserHandle.CURRENT + ); + } catch (SecurityException exception) { + Slog.e(TAG, "Unable to bind to MtpService due to SecurityException", exception); + } + + // Unbinding from the service if binding was not successful to release the connection. + // https://developer.android.com/reference/android/content/Context#bindService(android.content.Intent,%20android.content.ServiceConnection,%20int) + if (!mIsMtpServiceBound) { + unbindMtpService(); + Slog.e(TAG, "Binding to MtpService failed"); + } + + if (DEBUG && mIsMtpServiceBound) Slog.d(TAG, "Successfully bound to MtpService"); + } + + private void unbindMtpService() { + if (DEBUG) Slog.d(TAG, "Unbinding from MtpService"); + + mContext.unbindService(mMtpServiceConnection); + mIsMtpServiceBound = false; + } + private void setScreenUnlockedFunctions(int operationId) { setEnabledFunctions(mScreenUnlockedFunctions, false, operationId); } diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java index 40537c85784d..6d53c274cdf4 100644 --- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java +++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java @@ -16,6 +16,8 @@ package com.android.server.usb; +import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; + import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE; import android.Manifest; @@ -43,6 +45,7 @@ import android.os.AsyncTask; import android.os.Environment; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.service.usb.UsbProfileGroupSettingsManagerProto; import android.service.usb.UsbSettingsAccessoryPreferenceProto; import android.service.usb.UsbSettingsDevicePreferenceProto; @@ -939,13 +942,24 @@ public class UsbProfileGroupSettingsManager { } /** - * @return true if any application in foreground have set restrict_usb_overlay_activities as - * true in manifest file. The application needs to have MANAGE_USB permission. + * @return true if the user has not finished the setup process or if there are any + * foreground applications with MANAGE_USB permission and restrict_usb_overlay_activities + * enabled in the manifest file. */ private boolean shouldRestrictOverlayActivities() { if (!Flags.allowRestrictionOfOverlayActivities()) return false; + if (Settings.Secure.getIntForUser( + mContext.getContentResolver(), + USER_SETUP_COMPLETE, + /* defaultValue= */ 1, + UserHandle.CURRENT.getIdentifier()) + == 0) { + Slog.d(TAG, "restricting usb overlay activities as setup is not complete"); + return true; + } + List<ActivityManager.RunningAppProcessInfo> appProcessInfos = mActivityManager .getRunningAppProcesses(); diff --git a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig index a7c5ddb115b8..cd96d76a1c93 100644 --- a/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig +++ b/services/usb/java/com/android/server/usb/flags/usb_flags.aconfig @@ -7,3 +7,10 @@ flag { description: "This flag controls the restriction of usb overlay activities" bug: "307231174" } + +flag { + name: "enable_bind_to_mtp_service" + namespace: "usb" + description: "This flag enables binding to MtpService when in mtp/ptp modes" + bug: "332256525" +} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 4c719dd339e4..bc8f65edaa12 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3888,7 +3888,7 @@ public class CarrierConfigManager { /** * Whether device resets all of NR timers when device is in a voice call and QOS is established. - * The default value is false; + * The default value is true; * * @see #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING * @see #KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING @@ -10909,7 +10909,7 @@ public class CarrierConfigManager { sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, ""); sDefaults.putInt(KEY_NR_ADVANCED_BANDS_SECONDARY_TIMER_SECONDS_INT, 0); sDefaults.putBoolean(KEY_NR_TIMERS_RESET_IF_NON_ENDC_AND_RRC_IDLE_BOOL, false); - sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, false); + sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_VOICE_QOS_BOOL, true); sDefaults.putBoolean(KEY_NR_TIMERS_RESET_ON_PLMN_CHANGE_BOOL, false); /* Default value is 1 hour. */ sDefaults.putLong(KEY_5G_WATCHDOG_TIME_MS_LONG, 3600000); diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java index f161f319b0c2..0ecafc70b9a4 100644 --- a/telephony/java/android/telephony/PhoneNumberUtils.java +++ b/telephony/java/android/telephony/PhoneNumberUtils.java @@ -1283,6 +1283,8 @@ public class PhoneNumberUtils { private static final String JAPAN_ISO_COUNTRY_CODE = "JP"; + private static final String SINGAPORE_ISO_COUNTRY_CODE = "SG"; + /** * Breaks the given number down and formats it according to the rules * for the country the number is from. @@ -1669,6 +1671,17 @@ public class PhoneNumberUtils { * dialing format. */ result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); + } else if (Flags.removeCountryCodeFromLocalSingaporeCalls() && + (SINGAPORE_ISO_COUNTRY_CODE.equalsIgnoreCase(defaultCountryIso) && + pn.getCountryCode() == + util.getCountryCodeForRegion(SINGAPORE_ISO_COUNTRY_CODE) && + (pn.getCountryCodeSource() == + PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN))) { + /* + * Need to reformat Singaporean phone numbers (when the user is in Singapore) + * with the country code (+65) removed to comply with Singaporean regulations. + */ + result = util.format(pn, PhoneNumberUtil.PhoneNumberFormat.NATIONAL); } else { result = util.formatInOriginalFormat(pn, defaultCountryIso); } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 8fe45cbb0430..76b4e0052792 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -99,18 +99,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; /** - * Subscription manager provides the mobile subscription information that are associated with the - * calling user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U) - * and below can see all subscriptions as it does today. - * - * <p>For example, if we have - * <ul> - * <li> Subscription 1 associated with personal profile. - * <li> Subscription 2 associated with work profile. - * </ul> - * Then for SDK 35+, if the caller identity is personal profile, then - * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa. - * + * Subscription manager provides the mobile subscription information. */ @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) @@ -1980,17 +1969,7 @@ public class SubscriptionManager { } /** - * Get the SubscriptionInfo(s) of the currently active SIM(s) associated with the current caller - * user profile {@link UserHandle} for Android SDK 35(V) and above, while Android SDK 34(U) - * and below can see all subscriptions as it does today. - * - * <p>For example, if we have - * <ul> - * <li> Subscription 1 associated with personal profile. - * <li> Subscription 2 associated with work profile. - * </ul> - * Then for SDK 35+, if the caller identity is personal profile, then this will return - * subscription 1 only and vice versa. + * Get the SubscriptionInfo(s) of the currently active SIM(s). * * <p> Returned records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by * {@link SubscriptionInfo#getSubscriptionId}. Beginning with Android SDK 35, this method will @@ -2259,9 +2238,7 @@ public class SubscriptionManager { } /** - * Get the active subscription count associated with the current caller user profile for - * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as - * it does today. + * Get the active subscription count. * * @return The current number of active subscriptions. * diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 25e2d828068e..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); } /** @@ -13787,11 +13817,11 @@ public class TelephonyManager { * <p>This method returns valid data on devices with {@link * android.content.pm.PackageManager#FEATURE_TELEPHONY_CARRIERLOCK} enabled. * - * @deprecated Apps should use {@link getCarriersRestrictionRules} to retrieve the list of + * @deprecated Apps should use {@link #getCarrierRestrictionRules} to retrieve the list of * allowed and excliuded carriers, as the result of this API is valid only when the excluded * list is empty. This API could return an empty list, even if some restrictions are present. * - * @return List of {@link android.telephony.CarrierIdentifier}; empty list + * @return List of {@link android.service.carrier.CarrierIdentifier}; empty list * means all carriers are allowed. * * @throws UnsupportedOperationException If the device does not have 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..7d845a3c086c 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. @@ -3373,4 +3372,13 @@ interface ITelephony { + "android.Manifest.permission.SATELLITE_COMMUNICATION)") void unregisterForCommunicationAllowedStateChanged(int subId, in ISatelliteCommunicationAllowedStateCallback callback); + + /** + * This API can be used by only CTS to override the boolean configs used by the + * DatagramController module. + * + * @param enable Whether to enable boolean config. + * @return {@code true} if the boolean config is set successfully, {@code false} otherwise. + */ + boolean setDatagramControllerBooleanConfig(boolean reset, int booleanType, boolean enable); } 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/BinderLeakTest/Android.bp b/tests/BinderLeakTest/Android.bp index 78b0ede76d4e..3747d049417f 100644 --- a/tests/BinderLeakTest/Android.bp +++ b/tests/BinderLeakTest/Android.bp @@ -24,6 +24,9 @@ java_defaults { "androidx.test.rules", "androidx.test.runner", ], + test_suites: [ + "general-tests", + ], } // Built with target_sdk_version: current diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml index 6f8f008cf85b..955b43a32827 100644 --- a/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml +++ b/tests/FlickerTests/ActivityEmbedding/AndroidManifest.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.android.server.wm.flicker"> - <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/> + <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="35"/> <!-- Read and write traces from external storage --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> @@ -46,6 +46,8 @@ <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> <!-- Allow the test to connect to perfetto trace processor --> <uses-permission android:name="android.permission.INTERNET"/> + <!-- Allow to query for the Launcher TestInfo on SDK 30+ --> + <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <!-- Allow the test to write directly to /sdcard/ and connect to trace processor --> <application android:requestLegacyExternalStorage="true" android:networkSecurityConfig="@xml/network_security_config" diff --git a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml index 1dc103765c34..82de070921f0 100644 --- a/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml +++ b/tests/FlickerTests/ActivityEmbedding/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt index cf4edd50040b..67825d2df361 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt @@ -43,6 +43,7 @@ import org.junit.runners.Parameterized * * To run this test: `atest FlickerTestsOther:OpenTrampolineActivityTest` */ +@FlakyTest(bugId = 341209752) @RequiresDevice @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @@ -168,7 +169,6 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding } } - @FlakyTest(bugId = 290736037) /** Main activity should go from fullscreen to being a split with secondary activity. */ @Test fun mainActivityLayerGoesFromFullscreenToSplit() { @@ -203,7 +203,6 @@ class OpenTrampolineActivityTest(flicker: LegacyFlickerTest) : ActivityEmbedding } } - @FlakyTest(bugId = 288591571) @Test override fun visibleLayersShownMoreThanOneConsecutiveEntry() { super.visibleLayersShownMoreThanOneConsecutiveEntry() diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt index bc3696b3ed1c..eed9225d3da0 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt @@ -205,7 +205,8 @@ class SecondaryActivityEnterPipTest(flicker: LegacyFlickerTest) : it.visibleRegion(ComponentNameMatcher.PIP_CONTENT_OVERLAY) val secondaryVisibleRegion = it.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) - overlayVisibleRegion.coversExactly(secondaryVisibleRegion.region) + // TODO(b/340992001): replace coverAtLeast with coverExactly + overlayVisibleRegion.coversAtLeast(secondaryVisibleRegion.region) } .then() .isInvisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY) diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt index fb9258304870..379b45cdf08e 100644 --- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt +++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/splitscreen/EnterSystemSplitTest.kt @@ -60,14 +60,16 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa testApp.launchViaIntent(wmHelper) testApp.launchSecondaryActivity(wmHelper) secondaryApp.launchViaIntent(wmHelper) - tapl.goHome() - wmHelper - .StateSyncBuilder() - .withAppTransitionIdle() - .withHomeActivityVisible() - .waitForAndVerify() startDisplayBounds = wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + + // Record the displayBounds before `goHome()` in case the launcher is fixed-portrait. + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() } transitions { SplitScreenUtils.enterSplit( @@ -138,10 +140,6 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa check { "ActivityEmbeddingSplitHeight" } .that(leftAELayerRegion.region.bounds.height()) .isEqual(rightAELayerRegion.region.bounds.height()) - check { "SystemSplitHeight" } - .that(rightAELayerRegion.region.bounds.height()) - .isEqual(secondaryAppLayerRegion.region.bounds.height()) - // TODO(b/292283182): Remove this special case handling. check { "ActivityEmbeddingSplitWidth" } .that( abs( @@ -150,14 +148,6 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa ) ) .isLower(2) - check { "SystemSplitWidth" } - .that( - abs( - secondaryAppLayerRegion.region.bounds.width() - - 2 * rightAELayerRegion.region.bounds.width() - ) - ) - .isLower(2) } } @@ -170,15 +160,9 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) val rightAEWindowRegion = visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) - // There's no window for the divider bar. - val secondaryAppLayerRegion = - visibleRegion(ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent()) check { "ActivityEmbeddingSplitHeight" } .that(leftAEWindowRegion.region.bounds.height()) .isEqual(rightAEWindowRegion.region.bounds.height()) - check { "SystemSplitHeight" } - .that(rightAEWindowRegion.region.bounds.height()) - .isEqual(secondaryAppLayerRegion.region.bounds.height()) check { "ActivityEmbeddingSplitWidth" } .that( abs( @@ -187,14 +171,6 @@ class EnterSystemSplitTest(flicker: LegacyFlickerTest) : ActivityEmbeddingTestBa ) ) .isLower(2) - check { "SystemSplitWidth" } - .that( - abs( - secondaryAppLayerRegion.region.bounds.width() - - 2 * rightAEWindowRegion.region.bounds.width() - ) - ) - .isLower(2) } } diff --git a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml index 57a58c8377ec..4ffb11ab92ae 100644 --- a/tests/FlickerTests/AppClose/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppClose/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml index 2cb86e05f68b..0fa4d07b2eca 100644 --- a/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AppLaunch/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml index 2cf85fa38e67..4d9fefbc7d88 100644 --- a/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml +++ b/tests/FlickerTests/FlickerService/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/IME/AndroidTestTemplate.xml b/tests/FlickerTests/IME/AndroidTestTemplate.xml index b93e1bec21f8..b879c54dcab3 100644 --- a/tests/FlickerTests/IME/AndroidTestTemplate.xml +++ b/tests/FlickerTests/IME/AndroidTestTemplate.xml @@ -82,6 +82,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/IME/OWNERS b/tests/FlickerTests/IME/OWNERS index 301fafa5309e..e3a2e674ae7a 100644 --- a/tests/FlickerTests/IME/OWNERS +++ b/tests/FlickerTests/IME/OWNERS @@ -1,2 +1,3 @@ # ime # Bug component: 34867 +file:/services/core/java/com/android/server/inputmethod/OWNERS diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt index da8368f3cedf..2b6ddcb43f18 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt @@ -32,6 +32,9 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized +/** + * To run this test: `atest FlickerTestsIme1:CloseImeOnDismissPopupDialogTest` + */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt index 2f3ec6301215..0344197c1425 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeOnGoHomeTest.kt @@ -33,8 +33,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window closing to home transitions. To run this test: `atest - * FlickerTests:CloseImeWindowToHomeTest` + * Test IME window closing to home transitions. + * To run this test: `atest FlickerTestsIme1:CloseImeOnGoHomeTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt index 8821b69cdb3e..fde1373b032b 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartOnGoHomeTest.kt @@ -42,7 +42,7 @@ import org.junit.runners.Parameterized * * More details on b/190352379 * - * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToHomeTest` + * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartOnGoHomeTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt index d75eba68c7cc..dc5013519dbf 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt @@ -42,7 +42,7 @@ import org.junit.runners.Parameterized * * More details on b/190352379 * - * To run this test: `atest FlickerTests:CloseImeAutoOpenWindowToAppTest` + * To run this test: `atest FlickerTestsIme1:CloseImeShownOnAppStartToAppOnPressBackTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt index 41d9e30a17ee..dc2bd1bc9996 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToAppOnPressBackTest.kt @@ -34,8 +34,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window closing back to app window transitions. To run this test: `atest - * FlickerTests:CloseImeWindowToAppTest` + * Test IME window closing back to app window transitions. + * To run this test: `atest FlickerTestsIme1:CloseImeToAppOnPressBackTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt index 0e7fb7975df8..05771e88fc83 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized * Unlike {@link OpenImeWindowTest} testing IME window opening transitions, this test also verify * there is no flickering when back to the simple activity without requesting IME to show. * - * To run this test: `atest FlickerTests:OpenImeWindowAndCloseTest` + * To run this test: `atest FlickerTestsIme1:CloseImeToHomeOnFinishActivityTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt index 47a7e1b65b2d..336fe6f991ca 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/OpenImeWindowToFixedPortraitAppTest.kt @@ -36,8 +36,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window shown on the app with fixing portrait orientation. To run this test: `atest - * FlickerTests:OpenImeWindowToFixedPortraitAppTest` + * Test IME window shown on the app with fixing portrait orientation. + * To run this test: `atest FlickerTestsIme2:OpenImeWindowToFixedPortraitAppTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt index 48ec4d1fed2c..b8f11dcf8970 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest.kt @@ -38,8 +38,9 @@ import org.junit.runners.Parameterized /** * Test IME window layer will become visible when switching from the fixed orientation activity - * (e.g. Launcher activity). To run this test: `atest - * FlickerTests:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest` + * (e.g. Launcher activity). + * To run this test: + * `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromFixedOrientationTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt index 03f3a68a573f..34a708578396 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromOverviewTest.kt @@ -33,7 +33,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window opening transitions. To run this test: `atest FlickerTests:ReOpenImeWindowTest` + * Test IME window opening transitions. + * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromOverviewTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt index 7b62c8967628..7c72c3187a7f 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest.kt @@ -35,8 +35,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME windows switching with 2-Buttons or gestural navigation. To run this test: `atest - * FlickerTests:SwitchImeWindowsFromGestureNavTest` + * Test IME windows switching with 2-Buttons or gestural navigation. + * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppFromQuickSwitchTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt index 53bfb4ecf66f..fe5320cd1a46 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnAppStartWhenLaunchingAppTest.kt @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized /** * Launch an app that automatically displays the IME * - * To run this test: `atest FlickerTests:LaunchAppShowImeOnStartTest` + * To run this test: `atest FlickerTestsIme2:ShowImeOnAppStartWhenLaunchingAppTest` * * Actions: * ``` diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt index 31506b5eabf0..92b6b934874f 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeOnUnlockScreenTest.kt @@ -24,6 +24,7 @@ import android.tools.flicker.legacy.FlickerBuilder import android.tools.flicker.legacy.LegacyFlickerTest import android.tools.flicker.legacy.LegacyFlickerTestFactory import android.tools.traces.component.ComponentNameMatcher +import androidx.test.filters.FlakyTest import com.android.server.wm.flicker.BaseTest import com.android.server.wm.flicker.helpers.ImeAppHelper import org.junit.FixMethodOrder @@ -34,8 +35,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window closing on lock and opening on screen unlock. To run this test: `atest - * FlickerTests:CloseImeWindowToHomeTest` + * Test IME window closing on lock and opening on screen unlock. + * To run this test: `atest FlickerTestsIme2:ShowImeOnUnlockScreenTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @@ -90,6 +91,11 @@ class ShowImeOnUnlockScreenTest(flicker: LegacyFlickerTest) : BaseTest(flicker) @Ignore("Not applicable to this CUJ. Display turns off during transition") override fun taskBarWindowIsAlwaysVisible() {} + @FlakyTest(bugId = 338178020) + @Test + override fun visibleWindowsShownMoreThanOneConsecutiveEntry() = + super.visibleWindowsShownMoreThanOneConsecutiveEntry() + companion object { @Parameterized.Parameters(name = "{0}") @JvmStatic diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt index 12290af8fd46..9eaf998ed63f 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhenFocusingOnInputFieldTest.kt @@ -31,7 +31,10 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized -/** Test IME window opening transitions. To run this test: `atest FlickerTests:OpenImeWindowTest` */ +/** + * Test IME window opening transitions. + * To run this test: `atest FlickerTestsIme2:ShowImeWhenFocusingOnInputFieldTest` + */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt index 0948351ac65b..7186a2c48c4c 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileDismissingThemedPopupDialogTest.kt @@ -41,7 +41,7 @@ import org.junit.runners.Parameterized /** * Test IME snapshot mechanism won't apply when transitioning from non-IME focused dialog activity. - * To run this test: `atest FlickerTests:LaunchAppShowImeAndDialogThemeAppTest` + * To run this test: `atest FlickerTestsIme2:ShowImeWhileDismissingThemedPopupDialogTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt index 7aa525fcccef..c96c760e2d7b 100644 --- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt +++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt @@ -37,8 +37,8 @@ import org.junit.runners.MethodSorters import org.junit.runners.Parameterized /** - * Test IME window layer will be associated with the app task when going to the overview screen. To - * run this test: `atest FlickerTests:OpenImeWindowToOverViewTest` + * Test IME window layer will be associated with the app task when going to the overview screen. + * To run this test: `atest FlickerTestsIme2:ShowImeWhileEnteringOverviewTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/Notification/AndroidTestTemplate.xml b/tests/FlickerTests/Notification/AndroidTestTemplate.xml index 9c6a17d37a75..04b312a896b9 100644 --- a/tests/FlickerTests/Notification/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Notification/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt index ffaeeadb1042..8c9ab9aadb8e 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationColdTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized * * This test assumes the device doesn't have AOD enabled * - * To run this test: `atest FlickerTests:OpenAppFromLockNotificationCold` + * To run this test: `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationColdTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt index 6e67e193ed8c..e595100a2cbe 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt @@ -40,7 +40,7 @@ import org.junit.runners.Parameterized * * This test assumes the device doesn't have AOD enabled * - * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWarm` + * To run this test: `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationWarmTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt index f1df8a68fb63..fbe1d34272c9 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt @@ -40,7 +40,8 @@ import org.junit.runners.Parameterized * * This test assumes the device doesn't have AOD enabled * - * To run this test: `atest FlickerTests:OpenAppFromLockNotificationWithLockOverlayApp` + * To run this test: + * `atest FlickerTestsNotification:OpenAppFromLockscreenNotificationWithOverlayAppTest` */ @RequiresDevice @RunWith(Parameterized::class) diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt index b6d09d0bf3bb..c8ca644dde90 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationColdTest.kt @@ -36,7 +36,7 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from a notification. * - * To run this test: `atest FlickerTests:OpenAppFromNotificationCold` + * To run this test: `atest FlickerTestsNotification:OpenAppFromNotificationColdTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt index 1e607bfb2f49..c29e71ce4c79 100644 --- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt +++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt @@ -47,7 +47,7 @@ import org.junit.runners.Parameterized /** * Test cold launching an app from a notification. * - * To run this test: `atest FlickerTests:OpenAppFromNotificationWarm` + * To run this test: `atest FlickerTestsNotification:OpenAppFromNotificationWarmTest` */ @RunWith(Parameterized::class) @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) diff --git a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml index ecbed28085a2..8acdabc2337d 100644 --- a/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml +++ b/tests/FlickerTests/QuickSwitch/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/Rotation/Android.bp b/tests/FlickerTests/Rotation/Android.bp index b3eb934ef46d..aceb8bad256f 100644 --- a/tests/FlickerTests/Rotation/Android.bp +++ b/tests/FlickerTests/Rotation/Android.bp @@ -29,6 +29,10 @@ android_test { defaults: ["FlickerTestsDefault"], manifest: "AndroidManifest.xml", test_config_template: "AndroidTestTemplate.xml", + test_suites: [ + "device-tests", + "device-platinum-tests", + ], srcs: ["src/**/*"], static_libs: ["FlickerTestsBase"], data: ["trace_config/*"], diff --git a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml index 1eacdfd89384..91ece214aad5 100644 --- a/tests/FlickerTests/Rotation/AndroidTestTemplate.xml +++ b/tests/FlickerTests/Rotation/AndroidTestTemplate.xml @@ -80,6 +80,7 @@ value="trace_config.textproto" /> <option name="instrumentation-arg" key="per_run" value="true"/> + <option name="instrumentation-arg" key="perfetto_persist_pid_track" value="true"/> </test> <!-- Needed for pulling the collected trace config on to the host --> <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector"> diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 9198ae184b18..3e500d9c8bd4 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -18,7 +18,10 @@ package="com.android.server.wm.flicker.testapp"> <uses-sdk android:minSdkVersion="29" - android:targetSdkVersion="29"/> + android:targetSdkVersion="35"/> + + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> + <application android:allowBackup="false" android:supportsRtl="true"> <uses-library android:name="androidx.window.extensions" android:required="false"/> @@ -107,7 +110,7 @@ android:immersive="true" android:resizeableActivity="true" android:screenOrientation="portrait" - android:theme="@android:style/Theme.NoTitleBar" + android:theme="@style/OptOutEdgeToEdge.NoTitleBar" android:configChanges="screenSize" android:label="PortraitImmersiveActivity" android:exported="true"> @@ -119,7 +122,7 @@ <activity android:name=".LaunchTransparentActivity" android:resizeableActivity="false" android:screenOrientation="portrait" - android:theme="@android:style/Theme" + android:theme="@style/OptOutEdgeToEdge" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity" android:label="LaunchTransparentActivity" android:exported="true"> @@ -273,7 +276,7 @@ android:exported="true" android:label="MailActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.MailActivity" - android:theme="@style/Theme.AppCompat.Light"> + android:theme="@style/OptOutEdgeToEdge.AppCompatTheme"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -282,7 +285,7 @@ <activity android:name=".GameActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.GameActivity" android:immersive="true" - android:theme="@android:style/Theme.NoTitleBar" + android:theme="@style/OptOutEdgeToEdge.NoTitleBar" android:configChanges="screenSize" android:label="GameActivity" android:exported="true"> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml index 86c21906163f..917aec1e809d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml @@ -14,66 +14,71 @@ See the License for the specific language governing permissions and limitations under the License. --> -<LinearLayout +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" android:background="@android:color/holo_orange_light"> - <Button - android:id="@+id/launch_secondary_activity_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchSecondaryActivity" - android:tag="LEFT_TO_RIGHT" - android:text="Launch Secondary Activity" /> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> - <Button - android:id="@+id/launch_secondary_activity_rtl_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchSecondaryActivity" - android:tag="RIGHT_TO_LEFT" - android:text="Launch Secondary Activity in RTL" /> + <Button + android:id="@+id/launch_secondary_activity_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchSecondaryActivity" + android:tag="LEFT_TO_RIGHT" + android:text="Launch Secondary Activity" /> - <Button - android:id="@+id/launch_secondary_activity_horizontally_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchSecondaryActivity" - android:tag="BOTTOM_TO_TOP" - android:text="Launch Secondary Activity Horizontally" /> + <Button + android:id="@+id/launch_secondary_activity_rtl_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchSecondaryActivity" + android:tag="RIGHT_TO_LEFT" + android:text="Launch Secondary Activity in RTL" /> - <Button - android:id="@+id/launch_placeholder_split_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchPlaceholderSplit" - android:tag="LEFT_TO_RIGHT" - android:text="Launch Placeholder Split" /> + <Button + android:id="@+id/launch_secondary_activity_horizontally_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchSecondaryActivity" + android:tag="BOTTOM_TO_TOP" + android:text="Launch Secondary Activity Horizontally" /> - <Button - android:id="@+id/launch_always_expand_activity_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchAlwaysExpandActivity" - android:text="Launch Always Expand Activity" /> + <Button + android:id="@+id/launch_placeholder_split_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchPlaceholderSplit" + android:tag="LEFT_TO_RIGHT" + android:text="Launch Placeholder Split" /> - <Button - android:id="@+id/launch_placeholder_split_rtl_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchPlaceholderSplit" - android:tag="RIGHT_TO_LEFT" - android:text="Launch Placeholder Split in RTL" /> + <Button + android:id="@+id/launch_always_expand_activity_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchAlwaysExpandActivity" + android:text="Launch Always Expand Activity" /> - <Button - android:id="@+id/launch_trampoline_button" - android:layout_width="wrap_content" - android:layout_height="48dp" - android:onClick="launchTrampolineActivity" - android:tag="LEFT_TO_RIGHT" - android:text="Launch Trampoline Activity" /> + <Button + android:id="@+id/launch_placeholder_split_rtl_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchPlaceholderSplit" + android:tag="RIGHT_TO_LEFT" + android:text="Launch Placeholder Split in RTL" /> -</LinearLayout> + <Button + android:id="@+id/launch_trampoline_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:onClick="launchTrampolineActivity" + android:tag="LEFT_TO_RIGHT" + android:text="Launch Trampoline Activity" /> + + </LinearLayout> +</ScrollView> diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 9b742d96e35b..47d113717ae0 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -16,7 +16,19 @@ --> <resources> - <style name="DefaultTheme" parent="@android:style/Theme.DeviceDefault"> + <style name="OptOutEdgeToEdge" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + </style> + + <style name="OptOutEdgeToEdge.NoTitleBar" parent="@android:style/Theme.NoTitleBar"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + </style> + + <style name="OptOutEdgeToEdge.AppCompatTheme" parent="@style/Theme.AppCompat.Light"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> + </style> + + <style name="DefaultTheme" parent="@style/OptOutEdgeToEdge"> <item name="android:windowBackground">@android:color/darker_gray</item> </style> @@ -32,7 +44,7 @@ <item name="android:windowLayoutInDisplayCutoutMode">never</item> </style> - <style name="DialogTheme" parent="@android:style/Theme.DeviceDefault"> + <style name="DialogTheme" parent="@style/OptOutEdgeToEdge"> <item name="android:windowAnimationStyle">@null</item> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@null</item> @@ -43,18 +55,18 @@ <item name="android:windowSoftInputMode">stateUnchanged</item> </style> - <style name="TransparentTheme" parent="@android:style/Theme.DeviceDefault"> + <style name="TransparentTheme" parent="@style/OptOutEdgeToEdge"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> <item name="android:backgroundDimEnabled">false</item> </style> - <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault"> + <style name="no_starting_window" parent="@style/OptOutEdgeToEdge"> <item name="android:windowDisablePreview">true</item> </style> - <style name="SplashscreenAppTheme" parent="@android:style/Theme.DeviceDefault"> + <style name="SplashscreenAppTheme" parent="@style/OptOutEdgeToEdge"> <!-- Splashscreen Attributes --> <item name="android:windowSplashScreenAnimatedIcon">@drawable/avd_anim</item> <!-- Here we want to match the duration of our AVD --> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java index c92b82b896f2..a86ba5f76374 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/BubbleHelper.java @@ -125,7 +125,7 @@ public class BubbleHelper { .setContentTitle("BubbleChat") .setContentIntent(PendingIntent.getActivity(mContext, 0, new Intent(mContext, LaunchBubbleActivity.class), - PendingIntent.FLAG_UPDATE_CURRENT)) + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)) .setStyle(new Notification.MessagingStyle(chatBot) .setConversationTitle("BubbleChat") .addMessage("BubbleChat", @@ -140,7 +140,7 @@ public class BubbleHelper { Intent target = new Intent(mContext, BubbleActivity.class); target.putExtra(EXTRA_BUBBLE_NOTIF_ID, info.id); PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, info.id, target, - PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); return new Notification.BubbleMetadata.Builder() .setIntent(bubbleIntent) diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java index dea34442464d..37332c9712f5 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/LaunchBubbleActivity.java @@ -17,6 +17,9 @@ package com.android.server.wm.flicker.testapp; +import static android.Manifest.permission.POST_NOTIFICATIONS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.app.Activity; import android.app.Person; import android.content.Context; @@ -24,6 +27,7 @@ import android.content.Intent; import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; import android.graphics.drawable.Icon; +import android.os.Build; import android.os.Bundle; import android.view.View; @@ -36,6 +40,13 @@ public class LaunchBubbleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) { + // POST_NOTIFICATIONS permission required for notification post sdk 33. + requestPermissions(new String[] { POST_NOTIFICATIONS }, 0); + } + addInboxShortcut(getApplicationContext()); mBubbleHelper = BubbleHelper.getInstance(this); setContentView(R.layout.activity_main); diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java index a4dd5753539d..d6427abcc65a 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NotificationActivity.java @@ -16,6 +16,9 @@ package com.android.server.wm.flicker.testapp; +import static android.Manifest.permission.POST_NOTIFICATIONS; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + import android.app.Activity; import android.app.Notification; import android.app.NotificationChannel; @@ -23,6 +26,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import android.view.WindowManager; import android.widget.Button; @@ -34,6 +38,13 @@ public class NotificationActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && checkSelfPermission(POST_NOTIFICATIONS) != PERMISSION_GRANTED) { + // POST_NOTIFICATIONS permission required for notification post sdk 33. + requestPermissions(new String[] { POST_NOTIFICATIONS }, 0); + } + WindowManager.LayoutParams p = getWindow().getAttributes(); p.layoutInDisplayCutoutMode = WindowManager.LayoutParams .LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java index 1ab8ddbe20e2..27eb5a06451a 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/PipActivity.java @@ -198,7 +198,7 @@ public class PipActivity extends Activity { filter.addAction(ACTION_SET_REQUESTED_ORIENTATION); filter.addAction(ACTION_ENTER_PIP); filter.addAction(ACTION_ASPECT_RATIO); - registerReceiver(mBroadcastReceiver, filter); + registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); handleIntentExtra(getIntent()); } @@ -222,8 +222,8 @@ public class PipActivity extends Activity { private RemoteAction buildRemoteAction(Icon icon, String label, String action) { final Intent intent = new Intent(action); - final PendingIntent pendingIntent = - PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, + PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); return new RemoteAction(icon, label, label, pendingIntent); } diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index d17cd1fa2c3b..a85d809257cd 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -30,7 +30,7 @@ android_test { "androidx.test.rules", "androidx.test.runner", "androidx.test.uiautomator_uiautomator", - "servicestests-utils", + "compatibility-device-util-axt", "flag-junit", "frameworks-base-testutils", "hamcrest-library", @@ -38,6 +38,7 @@ android_test { "mockito-target-minus-junit4", "platform-test-annotations", "services.core.unboosted", + "servicestests-utils", "testables", "testng", "truth", diff --git a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt index 3a2a3be0690d..ae32bdaf80d7 100644 --- a/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt +++ b/tests/Input/src/android/hardware/input/KeyboardLayoutPreviewTests.kt @@ -16,6 +16,8 @@ package android.hardware.input +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.content.ContextWrapper import android.graphics.drawable.Drawable import android.platform.test.annotations.Presubmit @@ -54,16 +56,16 @@ class KeyboardLayoutPreviewTests { } @Test + @EnableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) fun testKeyboardLayoutDrawable_hasCorrectDimensions() { - setFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) val drawable = createDrawable()!! assertEquals(WIDTH, drawable.intrinsicWidth) assertEquals(HEIGHT, drawable.intrinsicHeight) } @Test + @DisableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) fun testKeyboardLayoutDrawable_isNull_ifFlagOff() { - setFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_LAYOUT_PREVIEW_FLAG) assertNull(createDrawable()) } }
\ No newline at end of file diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt index e2b0c36ae694..bcd56ad0c669 100644 --- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt +++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt @@ -21,6 +21,7 @@ import android.content.ContextWrapper import android.os.Handler import android.os.HandlerExecutor import android.os.test.TestLooper +import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.Presubmit import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent @@ -50,6 +51,10 @@ import kotlin.test.fail */ @Presubmit @RunWith(MockitoJUnitRunner::class) +@EnableFlags( + com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG, + com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL, +) class StickyModifierStateListenerTest { @get:Rule @@ -67,10 +72,6 @@ class StickyModifierStateListenerTest { @Before fun setUp() { - // Enable Sticky keys feature - rule.enableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG) - rule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL) - context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index c9c657404b68..3c72498082e4 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -28,22 +28,19 @@ import android.os.InputEventInjectionSync import android.os.SystemClock import android.os.test.TestLooper import android.platform.test.annotations.Presubmit -import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.provider.Settings import android.view.View.OnKeyListener -import android.view.Display import android.view.InputDevice import android.view.KeyEvent -import android.view.PointerIcon import android.view.SurfaceHolder import android.view.SurfaceView import android.test.mock.MockContentResolver import androidx.test.platform.app.InstrumentationRegistry import com.android.internal.util.test.FakeSettingsProvider import com.google.common.truth.Truth.assertThat +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity import org.junit.After -import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Rule @@ -52,22 +49,16 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.`when` -import org.mockito.Mockito.clearInvocations -import org.mockito.Mockito.doAnswer import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.verifyZeroInteractions import org.mockito.junit.MockitoJUnit import org.mockito.stubbing.OngoingStubbing -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit /** * Tests for {@link InputManagerService}. @@ -151,7 +142,9 @@ class InputManagerServiceTests { fun testInputSettingsUpdatedOnSystemRunning() { verifyZeroInteractions(native) - service.systemRunning() + runWithShellPermissionIdentity { + service.systemRunning() + } verify(native).setPointerSpeed(anyInt()) verify(native).setTouchpadPointerSpeed(anyInt()) @@ -176,203 +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) - } - - @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) - @Test - fun testSetVirtualMousePointerDisplayId() { - // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked - // until the native callback happens. - var countDownLatch = CountDownLatch(1) - val overrideDisplayId = 123 - Thread { - assertTrue("Setting virtual pointer display should succeed", - localService.setVirtualMousePointerDisplayId(overrideDisplayId)) - countDownLatch.countDown() - }.start() - assertFalse("Setting virtual pointer display should block", - countDownLatch.await(100, TimeUnit.MILLISECONDS)) - - val x = 42f - val y = 314f - service.onPointerDisplayIdChanged(overrideDisplayId, x, y) - testLooper.dispatchNext() - verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y) - assertTrue("Native callback unblocks calling thread", - countDownLatch.await(100, TimeUnit.MILLISECONDS)) - verify(native).setPointerDisplayId(overrideDisplayId) - - // Ensure that setting the same override again succeeds immediately. - assertTrue("Setting the same virtual mouse pointer displayId again should succeed", - localService.setVirtualMousePointerDisplayId(overrideDisplayId)) - - // Ensure that we did not query WM for the pointerDisplayId when setting the override - verify(wmCallbacks, never()).pointerDisplayId - - // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new - // pointer displayId and the calling thread is blocked until the native callback happens. - countDownLatch = CountDownLatch(1) - val pointerDisplayId = 42 - `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId) - Thread { - assertTrue("Unsetting virtual mouse pointer displayId should succeed", - localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY)) - countDownLatch.countDown() - }.start() - assertFalse("Unsetting virtual mouse pointer displayId should block", - countDownLatch.await(100, TimeUnit.MILLISECONDS)) - - service.onPointerDisplayIdChanged(pointerDisplayId, x, y) - testLooper.dispatchNext() - verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y) - assertTrue("Native callback unblocks calling thread", - countDownLatch.await(100, TimeUnit.MILLISECONDS)) - verify(native).setPointerDisplayId(pointerDisplayId) - } - - @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) - @Test - fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() { - // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked - // until the native callback happens. - val countDownLatch = CountDownLatch(1) - val overrideDisplayId = 123 - Thread { - assertFalse("Setting virtual pointer display should be unsuccessful", - localService.setVirtualMousePointerDisplayId(overrideDisplayId)) - countDownLatch.countDown() - }.start() - assertFalse("Setting virtual pointer display should block", - countDownLatch.await(100, TimeUnit.MILLISECONDS)) - - val x = 42f - val y = 314f - // Assume the native callback updates the pointerDisplayId to the incorrect value. - service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y) - testLooper.dispatchNext() - verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y) - assertTrue("Native callback unblocks calling thread", - countDownLatch.await(100, TimeUnit.MILLISECONDS)) - verify(native).setPointerDisplayId(overrideDisplayId) - } - - @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) - @Test - fun testSetVirtualMousePointerDisplayId_competingRequests() { - val firstRequestSyncLatch = CountDownLatch(1) - doAnswer { - firstRequestSyncLatch.countDown() - }.`when`(native).setPointerDisplayId(anyInt()) - - val firstRequestLatch = CountDownLatch(1) - val firstOverride = 123 - Thread { - assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful", - localService.setVirtualMousePointerDisplayId(firstOverride)) - firstRequestLatch.countDown() - }.start() - assertFalse("Setting virtual pointer display should block", - firstRequestLatch.await(100, TimeUnit.MILLISECONDS)) - - assertTrue("Wait for first thread's request should succeed", - firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS)) - - val secondRequestLatch = CountDownLatch(1) - val secondOverride = 42 - Thread { - assertTrue("Setting virtual mouse pointer from thread 2 should be successful", - localService.setVirtualMousePointerDisplayId(secondOverride)) - secondRequestLatch.countDown() - }.start() - assertFalse("Setting virtual mouse pointer should block", - secondRequestLatch.await(100, TimeUnit.MILLISECONDS)) - - val x = 42f - val y = 314f - // Assume the native callback updates directly to the second request. - service.onPointerDisplayIdChanged(secondOverride, x, y) - testLooper.dispatchNext() - verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y) - assertTrue("Native callback unblocks first thread", - firstRequestLatch.await(100, TimeUnit.MILLISECONDS)) - assertTrue("Native callback unblocks second thread", - secondRequestLatch.await(100, TimeUnit.MILLISECONDS)) - verify(native, times(2)).setPointerDisplayId(anyInt()) - } - - @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) - @Test - fun onDisplayRemoved_resetAllAdditionalInputProperties() { - setVirtualMousePointerDisplayIdAndVerify(10) - - localService.setPointerIconVisible(false, 10) - verify(native).setPointerIconVisibility(10, false) - verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) - localService.setMousePointerAccelerationEnabled(false, 10) - verify(native).setMousePointerAccelerationEnabled(10, false) - - service.onDisplayRemoved(10) - verify(native).setPointerIconVisibility(10, true) - verify(native).displayRemoved(eq(10)) - verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) - verify(native).setMousePointerAccelerationEnabled(10, true) - verifyNoMoreInteractions(native) - - // This call should not block because the virtual mouse pointer override was never removed. - localService.setVirtualMousePointerDisplayId(10) - - verify(native).setPointerDisplayId(eq(10)) - verifyNoMoreInteractions(native) - } - - @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) - @Test - fun updateAdditionalInputPropertiesForOverrideDisplay() { - setVirtualMousePointerDisplayIdAndVerify(10) - - localService.setPointerIconVisible(false, 10) - verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) - verify(native).setPointerIconVisibility(10, false) - localService.setMousePointerAccelerationEnabled(false, 10) - verify(native).setMousePointerAccelerationEnabled(10, false) - - localService.setPointerIconVisible(true, 10) - verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) - verify(native).setPointerIconVisibility(10, true) - localService.setMousePointerAccelerationEnabled(true, 10) - verify(native).setMousePointerAccelerationEnabled(10, true) - - localService.setPointerIconVisible(false, 20) - verify(native).setPointerIconVisibility(20, false) - localService.setMousePointerAccelerationEnabled(false, 20) - verify(native).setMousePointerAccelerationEnabled(20, false) - verifyNoMoreInteractions(native) - - clearInvocations(native) - setVirtualMousePointerDisplayIdAndVerify(20) - - verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) - } - - @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_ENABLE_POINTER_CHOREOGRAPHER) - @Test - fun setAdditionalInputPropertiesBeforeOverride() { - localService.setPointerIconVisible(false, 10) - localService.setMousePointerAccelerationEnabled(false, 10) - - verify(native).setPointerIconVisibility(10, false) - verify(native).setMousePointerAccelerationEnabled(10, false) - verifyNoMoreInteractions(native) - - setVirtualMousePointerDisplayIdAndVerify(10) - - verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) } @Test @@ -409,20 +205,6 @@ class InputManagerServiceTests { verify(native, times(2)).changeKeyboardLayoutAssociation() } - private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) { - val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) } - thread.start() - - // Allow some time for the set override call to park while waiting for the native callback. - Thread.sleep(100 /*millis*/) - verify(native).setPointerDisplayId(overrideDisplayId) - - service.onPointerDisplayIdChanged(overrideDisplayId, 0f, 0f) - testLooper.dispatchNext() - verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f) - thread.join(100 /*millis*/) - } - private fun createVirtualDisplays(count: Int): List<VirtualDisplay> { val displayManager: DisplayManager = context.getSystemService( DisplayManager::class.java @@ -529,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. @@ -552,7 +333,7 @@ class InputManagerServiceTests { val inputDevice = createInputDevice() // Associate input device with display - service.addUniqueIdAssociation( + service.addUniqueIdAssociationByPort( inputDevice.name, virtualDisplays[0].display.displayId.toString() ) @@ -576,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/Input/src/com/android/test/input/InputDeviceTest.java b/tests/Input/src/com/android/test/input/InputDeviceTest.java index 5f1bc8748db8..87a0de63120e 100644 --- a/tests/Input/src/com/android/test/input/InputDeviceTest.java +++ b/tests/Input/src/com/android/test/input/InputDeviceTest.java @@ -61,6 +61,7 @@ public class InputDeviceTest { assertEquals(device.getMotionRanges().size(), outDevice.getMotionRanges().size()); assertEquals(device.getHostUsiVersion(), outDevice.getHostUsiVersion()); assertEquals(device.getAssociatedDisplayId(), outDevice.getAssociatedDisplayId()); + assertEquals(device.isEnabled(), outDevice.isEnabled()); KeyCharacterMap keyCharacterMap = device.getKeyCharacterMap(); KeyCharacterMap outKeyCharacterMap = outDevice.getKeyCharacterMap(); @@ -100,7 +101,9 @@ public class InputDeviceTest { .setKeyboardLanguageTag("en-US") .setKeyboardLayoutType("qwerty") .setUsiVersion(new HostUsiVersion(2, 0)) - .setShouldSmoothScroll(true); + .setShouldSmoothScroll(true) + .setAssociatedDisplayId(Display.DEFAULT_DISPLAY) + .setEnabled(false); for (int i = 0; i < 30; i++) { deviceBuilder.addMotionRange( diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java index 001a09a0225a..be9fb1b309f6 100644 --- a/tests/Internal/src/com/android/internal/protolog/PerfettoDataSourceTest.java +++ b/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java @@ -34,7 +34,7 @@ import perfetto.protos.DataSourceConfigOuterClass; import perfetto.protos.ProtologCommon; import perfetto.protos.ProtologConfig; -public class PerfettoDataSourceTest { +public class ProtologDataSourceTest { @Before public void before() { assumeTrue(android.tracing.Flags.perfettoProtologTracing()); 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/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp index f0bea3f3c28a..2909e66b53be 100644 --- a/tests/UsbManagerTests/Android.bp +++ b/tests/UsbManagerTests/Android.bp @@ -43,6 +43,9 @@ android_test { "libmultiplejvmtiagentsinterferenceagent", "libstaticjvmtiagent", ], + libs: [ + "android.test.mock", + ], certificate: "platform", platform_apis: true, test_suites: ["device-tests"], diff --git a/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java new file mode 100644 index 000000000000..d6f3148e64f1 --- /dev/null +++ b/tests/UsbManagerTests/src/android/hardware/usb/DeviceFilterTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.usb; + +import static com.google.common.truth.Truth.assertThat; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.usb.flags.Flags; + +import androidx.test.runner.AndroidJUnit4; + +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.util.XmlUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlSerializer; + +import java.io.StringReader; + +/** + * Unit tests for {@link android.hardware.usb.DeviceFilter}. + */ +@RunWith(AndroidJUnit4.class) +public class DeviceFilterTest { + + private static final int VID = 10; + private static final int PID = 11; + private static final int CLASS = 12; + private static final int SUBCLASS = 13; + private static final int PROTOCOL = 14; + private static final String MANUFACTURER = "Google"; + private static final String PRODUCT = "Test"; + private static final String SERIAL_NO = "4AL23"; + private static final String INTERFACE_NAME = "MTP"; + + private MockitoSession mStaticMockSession; + + @Before + public void setUp() throws Exception { + mStaticMockSession = ExtendedMockito.mockitoSession() + .mockStatic(Flags.class) + .strictness(Strictness.WARN) + .startMocking(); + + when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(true); + } + + @After + public void tearDown() throws Exception { + mStaticMockSession.finishMocking(); + } + + @Test + public void testConstructorFromValues_interfaceNameIsInitialized() { + DeviceFilter deviceFilter = new DeviceFilter( + VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER, + PRODUCT, SERIAL_NO, INTERFACE_NAME + ); + + verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter); + assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME); + } + + @Test + public void testConstructorFromUsbDevice_interfaceNameIsNull() { + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + when(usbDevice.getVendorId()).thenReturn(VID); + when(usbDevice.getProductId()).thenReturn(PID); + when(usbDevice.getDeviceClass()).thenReturn(CLASS); + when(usbDevice.getDeviceSubclass()).thenReturn(SUBCLASS); + when(usbDevice.getDeviceProtocol()).thenReturn(PROTOCOL); + when(usbDevice.getManufacturerName()).thenReturn(MANUFACTURER); + when(usbDevice.getProductName()).thenReturn(PRODUCT); + when(usbDevice.getSerialNumber()).thenReturn(SERIAL_NO); + + DeviceFilter deviceFilter = new DeviceFilter(usbDevice); + + verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter); + assertThat(deviceFilter.mInterfaceName).isEqualTo(null); + } + + @Test + public void testConstructorFromDeviceFilter_interfaceNameIsInitialized() { + DeviceFilter originalDeviceFilter = new DeviceFilter( + VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER, + PRODUCT, SERIAL_NO, INTERFACE_NAME + ); + + DeviceFilter deviceFilter = new DeviceFilter(originalDeviceFilter); + + verifyDeviceFilterConfigurationExceptInterfaceName(deviceFilter); + assertThat(deviceFilter.mInterfaceName).isEqualTo(INTERFACE_NAME); + } + + + @Test + public void testReadFromXml_interfaceNamePresent_propertyIsInitialized() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>"); + + assertThat(deviceFilter.mInterfaceName).isEqualTo("MTP"); + } + + @Test + public void testReadFromXml_interfaceNameAbsent_propertyIsNull() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />"); + + assertThat(deviceFilter.mInterfaceName).isEqualTo(null); + } + + @Test + public void testWrite_withInterfaceName() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device interface-name=\"MTP\"/>"); + XmlSerializer serializer = Mockito.mock(XmlSerializer.class); + + deviceFilter.write(serializer); + + verify(serializer).attribute(null, "interface-name", "MTP"); + } + + @Test + public void testWrite_withoutInterfaceName() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml("<usb-device vendor-id=\"1\" />"); + XmlSerializer serializer = Mockito.mock(XmlSerializer.class); + + deviceFilter.write(serializer); + + verify(serializer, times(0)).attribute(eq(null), eq("interface-name"), any()); + } + + @Test + public void testToString() { + DeviceFilter deviceFilter = new DeviceFilter( + VID, PID, CLASS, SUBCLASS, PROTOCOL, MANUFACTURER, + PRODUCT, SERIAL_NO, INTERFACE_NAME + ); + + assertThat(deviceFilter.toString()).isEqualTo( + "DeviceFilter[mVendorId=10,mProductId=11,mClass=12,mSubclass=13,mProtocol=14," + + "mManufacturerName=Google,mProductName=Test,mSerialNumber=4AL23," + + "mInterfaceName=MTP]"); + } + + @Test + public void testMatch_interfaceNameMatches_returnTrue() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml( + "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" " + + "interface-name=\"MTP\"/>"); + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + when(usbDevice.getInterfaceCount()).thenReturn(1); + when(usbDevice.getInterface(0)).thenReturn(new UsbInterface( + /* id= */ 0, + /* alternateSetting= */ 0, + /* name= */ "MTP", + /* class= */ 255, + /* subClass= */ 255, + /* protocol= */ 0)); + + assertTrue(deviceFilter.matches(usbDevice)); + } + + @Test + public void testMatch_interfaceNameMismatch_returnFalse() throws Exception { + DeviceFilter deviceFilter = getDeviceFilterFromXml( + "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" " + + "interface-name=\"MTP\"/>"); + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + when(usbDevice.getInterfaceCount()).thenReturn(1); + when(usbDevice.getInterface(0)).thenReturn(new UsbInterface( + /* id= */ 0, + /* alternateSetting= */ 0, + /* name= */ "UVC", + /* class= */ 255, + /* subClass= */ 255, + /* protocol= */ 0)); + + assertFalse(deviceFilter.matches(usbDevice)); + } + + @Test + public void testMatch_interfaceNameMismatchFlagDisabled_returnTrue() throws Exception { + when(Flags.enableInterfaceNameDeviceFilter()).thenReturn(false); + DeviceFilter deviceFilter = getDeviceFilterFromXml( + "<usb-device class=\"255\" subclass=\"255\" protocol=\"0\" " + + "interface-name=\"MTP\"/>"); + UsbDevice usbDevice = Mockito.mock(UsbDevice.class); + when(usbDevice.getInterfaceCount()).thenReturn(1); + when(usbDevice.getInterface(0)).thenReturn(new UsbInterface( + /* id= */ 0, + /* alternateSetting= */ 0, + /* name= */ "UVC", + /* class= */ 255, + /* subClass= */ 255, + /* protocol= */ 0)); + + assertTrue(deviceFilter.matches(usbDevice)); + } + + private void verifyDeviceFilterConfigurationExceptInterfaceName(DeviceFilter deviceFilter) { + assertThat(deviceFilter.mVendorId).isEqualTo(VID); + assertThat(deviceFilter.mProductId).isEqualTo(PID); + assertThat(deviceFilter.mClass).isEqualTo(CLASS); + assertThat(deviceFilter.mSubclass).isEqualTo(SUBCLASS); + assertThat(deviceFilter.mProtocol).isEqualTo(PROTOCOL); + assertThat(deviceFilter.mManufacturerName).isEqualTo(MANUFACTURER); + assertThat(deviceFilter.mProductName).isEqualTo(PRODUCT); + assertThat(deviceFilter.mSerialNumber).isEqualTo(SERIAL_NO); + } + + private DeviceFilter getDeviceFilterFromXml(String xml) throws Exception { + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser parser = factory.newPullParser(); + parser.setInput(new StringReader(xml)); + XmlUtils.nextElement(parser); + + return DeviceFilter.read(parser); + } + +} diff --git a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java index 4780d8a610e8..87b26a63acc7 100644 --- a/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java +++ b/tests/UsbManagerTests/src/com/android/server/usbtest/UsbProfileGroupSettingsManagerTest.java @@ -16,6 +16,8 @@ package com.android.server.usbtest; +import static android.provider.Settings.Secure.USER_SETUP_COMPLETE; + import static com.android.server.usb.UsbProfileGroupSettingsManager.PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES; import static org.mockito.ArgumentMatchers.any; @@ -32,16 +34,20 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.Property; import android.content.pm.UserInfo; import android.content.res.Resources; import android.hardware.usb.UsbDevice; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; +import android.test.mock.MockContentResolver; import androidx.test.runner.AndroidJUnit4; import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.internal.util.test.FakeSettingsProvider; import com.android.server.usb.UsbHandlerManager; import com.android.server.usb.UsbProfileGroupSettingsManager; import com.android.server.usb.UsbSettingsManager; @@ -69,6 +75,7 @@ import java.util.List; public class UsbProfileGroupSettingsManagerTest { private static final String TEST_PACKAGE_NAME = "testPkg"; + @Mock private Context mContext; @Mock @@ -85,43 +92,78 @@ public class UsbProfileGroupSettingsManagerTest { private UserManager mUserManager; @Mock private UsbUserSettingsManager mUsbUserSettingsManager; - @Mock private Property mProperty; - private ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo; - private PackageInfo mPackageInfo; - private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager; + @Mock + private Property mRestrictUsbOverlayActivitiesProperty; + @Mock + private UsbDevice mUsbDevice; + + private MockContentResolver mContentResolver; private MockitoSession mStaticMockSession; + private UsbProfileGroupSettingsManager mUsbProfileGroupSettingsManager; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mStaticMockSession = ExtendedMockito.mockitoSession() + .mockStatic(Flags.class) + .strictness(Strictness.WARN) + .startMocking(); - mRunningAppProcessInfo = new ActivityManager.RunningAppProcessInfo(); - mRunningAppProcessInfo.pkgList = new String[]{TEST_PACKAGE_NAME}; - mPackageInfo = new PackageInfo(); - mPackageInfo.packageName = TEST_PACKAGE_NAME; - mPackageInfo.applicationInfo = Mockito.mock(ApplicationInfo.class); + when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager); + when(mUserManager.getEnabledProfiles(anyInt())) + .thenReturn(List.of(Mockito.mock(UserInfo.class))); + + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + when(mContext.getContentResolver()).thenReturn(mContentResolver); when(mContext.getPackageManager()).thenReturn(mPackageManager); - when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager); when(mContext.getResources()).thenReturn(Mockito.mock(Resources.class)); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mContext.getSystemService(ActivityManager.class)).thenReturn(mActivityManager); when(mContext.createPackageContextAsUser(anyString(), anyInt(), any(UserHandle.class))) .thenReturn(mContext); - when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); - mUsbProfileGroupSettingsManager = new UsbProfileGroupSettingsManager(mContext, mUserHandle, - mUsbSettingsManager, mUsbHandlerManager); + mUsbProfileGroupSettingsManager = new UsbProfileGroupSettingsManager( + mContext, mUserHandle, mUsbSettingsManager, mUsbHandlerManager); - mStaticMockSession = ExtendedMockito.mockitoSession() - .mockStatic(Flags.class) - .strictness(Strictness.WARN) - .startMocking(); + setupDefaultConfiguration(); + } + /** + * Setups the following configuration + * + * <ul> + * <li>Flag is enabled + * <li>Device setup has completed + * <li>There is a foreground activity with MANAGE_USB permission + * <li>The foreground activity has PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES enabled + * </ul> + */ + private void setupDefaultConfiguration() throws NameNotFoundException { + when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true); + + Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 1); + + ActivityManager.RunningAppProcessInfo mRunningAppProcessInfo = + new ActivityManager.RunningAppProcessInfo(); + mRunningAppProcessInfo.pkgList = new String[] { TEST_PACKAGE_NAME }; + when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo)); + + PackageInfo mPackageInfo = new PackageInfo(); + mPackageInfo.packageName = TEST_PACKAGE_NAME; + mPackageInfo.applicationInfo = Mockito.mock(ApplicationInfo.class); when(mPackageManager.getPackageInfo(TEST_PACKAGE_NAME, 0)).thenReturn(mPackageInfo); - when(mPackageManager.getProperty(eq(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES), - eq(TEST_PACKAGE_NAME))).thenReturn(mProperty); - when(mUserManager.getEnabledProfiles(anyInt())) - .thenReturn(List.of(Mockito.mock(UserInfo.class))); - when(mUsbSettingsManager.getSettingsForUser(anyInt())).thenReturn(mUsbUserSettingsManager); + when(mPackageManager.getPackagesHoldingPermissions( + new String[] { android.Manifest.permission.MANAGE_USB }, + PackageManager.MATCH_SYSTEM_ONLY)) + .thenReturn(List.of(mPackageInfo)); + + when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(true); + when(mPackageManager.getProperty( + eq(PROPERTY_RESTRICT_USB_OVERLAY_ACTIVITIES), eq(TEST_PACKAGE_NAME))) + .thenReturn(mRestrictUsbOverlayActivitiesProperty); } @After @@ -130,66 +172,59 @@ public class UsbProfileGroupSettingsManagerTest { } @Test - public void testDeviceAttached_flagTrueWithoutForegroundActivity_resolveActivityCalled() { - when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true); + public void testDeviceAttached_foregroundActivityWithManifestField_resolveActivityNotCalled() { + mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice); + + verify(mUsbUserSettingsManager, times(0)).queryIntentActivities(any(Intent.class)); + } + + @Test + public void testDeviceAttached_noForegroundActivity_resolveActivityCalled() { when(mActivityManager.getRunningAppProcesses()).thenReturn(new ArrayList<>()); - when(mPackageManager.getPackagesHoldingPermissions( - new String[]{android.Manifest.permission.MANAGE_USB}, - PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo)); - UsbDevice device = Mockito.mock(UsbDevice.class); - mUsbProfileGroupSettingsManager.deviceAttached(device); + + mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice); + verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class)); } @Test public void testDeviceAttached_noForegroundActivityWithUsbPermission_resolveActivityCalled() { - when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true); - when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo)); when(mPackageManager.getPackagesHoldingPermissions( - new String[]{android.Manifest.permission.MANAGE_USB}, - PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(new ArrayList<>()); - UsbDevice device = Mockito.mock(UsbDevice.class); - mUsbProfileGroupSettingsManager.deviceAttached(device); + new String[] { android.Manifest.permission.MANAGE_USB }, + PackageManager.MATCH_SYSTEM_ONLY)) + .thenReturn(new ArrayList<>()); + + mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice); + verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class)); } @Test - public void testDeviceAttached_foregroundActivityWithManifestField_resolveActivityNotCalled() { - when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true); - when(mProperty.getBoolean()).thenReturn(true); - when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo)); - when(mPackageManager.getPackagesHoldingPermissions( - new String[]{android.Manifest.permission.MANAGE_USB}, - PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo)); - UsbDevice device = Mockito.mock(UsbDevice.class); - mUsbProfileGroupSettingsManager.deviceAttached(device); - verify(mUsbUserSettingsManager, times(0)) - .queryIntentActivities(any(Intent.class)); - } + public void testDeviceAttached_restricUsbOverlayPropertyDisabled_resolveActivityCalled() { + when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(false); + + mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice); - @Test - public void testDeviceAttached_foregroundActivityWithoutManifestField_resolveActivityCalled() { - when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(true); - when(mProperty.getBoolean()).thenReturn(false); - when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo)); - when(mPackageManager.getPackagesHoldingPermissions( - new String[]{android.Manifest.permission.MANAGE_USB}, - PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo)); - UsbDevice device = Mockito.mock(UsbDevice.class); - mUsbProfileGroupSettingsManager.deviceAttached(device); verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class)); } @Test - public void testDeviceAttached_flagFalseForegroundActivity_resolveActivityCalled() { + public void testDeviceAttached_flagFalse_resolveActivityCalled() { when(Flags.allowRestrictionOfOverlayActivities()).thenReturn(false); - when(mProperty.getBoolean()).thenReturn(true); - when(mActivityManager.getRunningAppProcesses()).thenReturn(List.of(mRunningAppProcessInfo)); - when(mPackageManager.getPackagesHoldingPermissions( - new String[]{android.Manifest.permission.MANAGE_USB}, - PackageManager.MATCH_SYSTEM_ONLY)).thenReturn(List.of(mPackageInfo)); - UsbDevice device = Mockito.mock(UsbDevice.class); - mUsbProfileGroupSettingsManager.deviceAttached(device); + + mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice); + verify(mUsbUserSettingsManager).queryIntentActivities(any(Intent.class)); } + + @Test + public void + testDeviceAttached_setupNotCompleteAndNoBlockingActivities_resolveActivityNotCalled() { + when(mRestrictUsbOverlayActivitiesProperty.getBoolean()).thenReturn(false); + Settings.Secure.putInt(mContentResolver, USER_SETUP_COMPLETE, 0); + + mUsbProfileGroupSettingsManager.deviceAttached(mUsbDevice); + + verify(mUsbUserSettingsManager, times(0)).queryIntentActivities(any(Intent.class)); + } } 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/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 960b57cb632a..580efe126ea3 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -70,6 +70,7 @@ import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.Uri; +import android.net.vcn.Flags; import android.net.vcn.IVcnStatusCallback; import android.net.vcn.IVcnUnderlyingNetworkPolicyListener; import android.net.vcn.VcnConfig; @@ -82,7 +83,9 @@ import android.os.ParcelUuid; import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; +import android.os.UserManager; import android.os.test.TestLooper; +import android.platform.test.flag.junit.SetFlagsRule; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; @@ -101,6 +104,7 @@ import com.android.server.vcn.util.PersistableBundleUtils; import com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -118,6 +122,8 @@ import java.util.UUID; @RunWith(AndroidJUnit4.class) @SmallTest public class VcnManagementServiceTest { + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private static final String CONTEXT_ATTRIBUTION_TAG = "VCN"; private static final String TEST_PACKAGE_NAME = VcnManagementServiceTest.class.getPackage().getName(); @@ -129,7 +135,12 @@ public class VcnManagementServiceTest { private static final ParcelUuid TEST_UUID_3 = new ParcelUuid(new UUID(2, 2)); private static final VcnConfig TEST_VCN_CONFIG; private static final VcnConfig TEST_VCN_CONFIG_PKG_2; - private static final int TEST_UID = Process.FIRST_APPLICATION_UID; + + private static final int TEST_UID = 1010000; // A non-system user + private static final UserHandle TEST_USER_HANDLE = UserHandle.getUserHandleForUid(TEST_UID); + private static final UserHandle TEST_USER_HANDLE_OTHER = + UserHandle.of(TEST_USER_HANDLE.getIdentifier() + 1); + private static final String TEST_IFACE_NAME = "TEST_IFACE"; private static final String TEST_IFACE_NAME_2 = "TEST_IFACE2"; private static final LinkProperties TEST_LP_1 = new LinkProperties(); @@ -187,6 +198,7 @@ public class VcnManagementServiceTest { private final TelephonyManager mTelMgr = mock(TelephonyManager.class); private final SubscriptionManager mSubMgr = mock(SubscriptionManager.class); private final AppOpsManager mAppOpsMgr = mock(AppOpsManager.class); + private final UserManager mUserManager = mock(UserManager.class); private final VcnContext mVcnContext = mock(VcnContext.class); private final PersistableBundleUtils.LockingReadWriteHelper mConfigReadWriteHelper = mock(PersistableBundleUtils.LockingReadWriteHelper.class); @@ -218,6 +230,9 @@ public class VcnManagementServiceTest { Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class); setupSystemService(mMockContext, mAppOpsMgr, Context.APP_OPS_SERVICE, AppOpsManager.class); + setupSystemService(mMockContext, mUserManager, Context.USER_SERVICE, UserManager.class); + + doReturn(TEST_USER_HANDLE).when(mUserManager).getMainUser(); doReturn(TEST_PACKAGE_NAME).when(mMockContext).getOpPackageName(); @@ -267,6 +282,8 @@ public class VcnManagementServiceTest { @Before public void setUp() { + mSetFlagsRule.enableFlags(Flags.FLAG_ENFORCE_MAIN_USER); + doNothing() .when(mMockContext) .enforceCallingOrSelfPermission( @@ -717,10 +734,8 @@ public class VcnManagementServiceTest { } @Test - public void testSetVcnConfigRequiresSystemUser() throws Exception { - doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) - .when(mMockDeps) - .getBinderCallingUid(); + public void testSetVcnConfigRequiresMainUser() throws Exception { + doReturn(TEST_USER_HANDLE_OTHER).when(mUserManager).getMainUser(); try { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); @@ -832,10 +847,8 @@ public class VcnManagementServiceTest { } @Test - public void testClearVcnConfigRequiresSystemUser() throws Exception { - doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) - .when(mMockDeps) - .getBinderCallingUid(); + public void testClearVcnConfigRequiresMainUser() throws Exception { + doReturn(TEST_USER_HANDLE_OTHER).when(mUserManager).getMainUser(); try { mVcnMgmtSvc.clearVcnConfig(TEST_UUID_1, TEST_PACKAGE_NAME); @@ -921,10 +934,8 @@ public class VcnManagementServiceTest { } @Test - public void testGetConfiguredSubscriptionGroupsRequiresSystemUser() throws Exception { - doReturn(UserHandle.getUid(UserHandle.MIN_SECONDARY_USER_ID, TEST_UID)) - .when(mMockDeps) - .getBinderCallingUid(); + public void testGetConfiguredSubscriptionGroupsRequiresMainUser() throws Exception { + doReturn(TEST_USER_HANDLE_OTHER).when(mUserManager).getMainUser(); try { mVcnMgmtSvc.getConfiguredSubscriptionGroups(TEST_PACKAGE_NAME); diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java index c8b60e5c335f..441a4ae6d9b6 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java @@ -20,6 +20,7 @@ import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY; import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY; +import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR; import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM; import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond; import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper; @@ -584,4 +585,56 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase { MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED, getMaxSeqNumIncreasePerSecond(mCarrierConfig)); } + + private IpSecPacketLossDetector newDetectorAndSetTransform(int threshold) throws Exception { + when(mCarrierConfig.getInt( + eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY), + anyInt())) + .thenReturn(threshold); + + final IpSecPacketLossDetector detector = + new IpSecPacketLossDetector( + mVcnContext, + mNetwork, + mCarrierConfig, + mMetricMonitorCallback, + mDependencies); + + detector.setIsSelectedUnderlyingNetwork(true /* setIsSelected */); + detector.setInboundTransformInternal(mIpSecTransform); + + return detector; + } + + @Test + public void testDisableAndEnableDetectorWithCarrierConfig() throws Exception { + final IpSecPacketLossDetector detector = + newDetectorAndSetTransform(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR); + + assertFalse(detector.isStarted()); + + when(mCarrierConfig.getInt( + eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY), + anyInt())) + .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD); + detector.setCarrierConfig(mCarrierConfig); + + assertTrue(detector.isStarted()); + } + + @Test + public void testEnableAndDisableDetectorWithCarrierConfig() throws Exception { + final IpSecPacketLossDetector detector = + newDetectorAndSetTransform(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD); + + assertTrue(detector.isStarted()); + + when(mCarrierConfig.getInt( + eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY), + anyInt())) + .thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DISABLE_DETECTOR); + detector.setCarrierConfig(mCarrierConfig); + + assertFalse(detector.isStarted()); + } } diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java index edad67896e8e..0439d5f54e23 100644 --- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java @@ -123,6 +123,7 @@ public abstract class NetworkEvaluationTestBase { mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS); mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE); mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP); + mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_DISABLE_IPSEC_LOSS_DETECTOR); when(mNetwork.getNetId()).thenReturn(-1); diff --git a/tools/aapt/Symbol.h b/tools/aapt/Symbol.h index de1d60cbae42..24c3208d9081 100644 --- a/tools/aapt/Symbol.h +++ b/tools/aapt/Symbol.h @@ -40,7 +40,7 @@ struct Symbol { }; /** - * A specific defintion of a symbol, defined with a configuration and a definition site. + * A specific definition of a symbol, defined with a configuration and a definition site. */ struct SymbolDefinition { inline SymbolDefinition(); @@ -92,4 +92,3 @@ bool SymbolDefinition::operator<(const SymbolDefinition& rhs) const { } #endif // AAPT_SYMBOL_H - diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index f1e4ead03314..669cddb9af5a 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -443,7 +443,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn manifest_action.Action(AutoGenerateIsSplitRequired); manifest_action.Action(VerifyManifest); manifest_action.Action(FixCoreAppAttribute); - manifest_action.Action([&](xml::Element* el) -> bool { + manifest_action.Action([this, diag](xml::Element* el) -> bool { EnsureNamespaceIsDeclared("android", xml::kSchemaAndroid, &el->namespace_decls); if (options_.version_name_default) { @@ -506,7 +506,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn manifest_action["eat-comment"]; // Uses-sdk actions. - manifest_action["uses-sdk"].Action([&](xml::Element* el) -> bool { + manifest_action["uses-sdk"].Action([this](xml::Element* el) -> bool { if (options_.min_sdk_version_default && el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) { // There was no minSdkVersion defined and we have a default to assign. @@ -528,7 +528,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn // Instrumentation actions. manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName); - manifest_action["instrumentation"].Action([&](xml::Element* el) -> bool { + manifest_action["instrumentation"].Action([this](xml::Element* el) -> bool { if (!options_.rename_instrumentation_target_package) { return true; } @@ -544,7 +544,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn manifest_action["attribution"]; manifest_action["attribution"]["inherit-from"]; manifest_action["original-package"]; - manifest_action["overlay"].Action([&](xml::Element* el) -> bool { + manifest_action["overlay"].Action([this](xml::Element* el) -> bool { if (options_.rename_overlay_target_package) { if (xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "targetPackage")) { attr->value = options_.rename_overlay_target_package.value(); @@ -625,7 +625,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn uses_package_action["additional-certificate"]; if (options_.debug_mode) { - application_action.Action([&](xml::Element* el) -> bool { + application_action.Action([](xml::Element* el) -> bool { xml::Attribute *attr = el->FindOrCreateAttribute(xml::kSchemaAndroid, "debuggable"); attr->value = "true"; return true; 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/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt index b8d18001f37b..3f2b13aed5c0 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt @@ -290,6 +290,16 @@ fun FieldNode.getVisibility(): Visibility { return Visibility.fromAccess(this.access) } +/** Return the [access] flags without the visibility */ +fun clearVisibility(access: Int): Int { + return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE).inv() +} + +/** Return the visibility part of the [access] flags */ +fun getVisibility(access: Int): Int { + return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE) +} + /* diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt index 16785d1d9598..6b360b79c327 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt @@ -25,6 +25,7 @@ class AndroidHeuristicsFilter( val aidlPolicy: FilterPolicyWithReason?, val featureFlagsPolicy: FilterPolicyWithReason?, val syspropsPolicy: FilterPolicyWithReason?, + val rFilePolicy: FilterPolicyWithReason?, fallback: OutputFilter ) : DelegatingFilter(fallback) { override fun getPolicyForClass(className: String): FilterPolicyWithReason { @@ -37,6 +38,9 @@ class AndroidHeuristicsFilter( if (syspropsPolicy != null && classes.isSyspropsClass(className)) { return syspropsPolicy } + if (rFilePolicy != null && classes.isRClass(className)) { + return rFilePolicy + } return super.getPolicyForClass(className) } } @@ -74,3 +78,10 @@ private fun ClassNodes.isSyspropsClass(className: String): Boolean { return className.startsWith("android/sysprop/") && className.endsWith("Properties") } + +/** + * @return if a given class "seems like" an R class or its nested classes. + */ +private fun ClassNodes.isRClass(className: String): Boolean { + return className.endsWith("/R") || className.contains("/R$") +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt index 5659a35170c1..2e144f5513bc 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt @@ -15,11 +15,11 @@ */ package com.android.hoststubgen.filters -import com.android.hoststubgen.UnknownApiException import com.android.hoststubgen.addNonNullElement import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.asm.toHumanReadableMethodName +import com.android.hoststubgen.log // TODO: Validate all input names. @@ -48,30 +48,30 @@ class InMemoryOutputFilter( return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className) } - private fun ensureClassExists(className: String) { + private fun checkClass(className: String) { if (classes.findClass(className) == null) { - throw UnknownApiException("Unknown class $className") + log.w("Unknown class $className") } } - private fun ensureFieldExists(className: String, fieldName: String) { + private fun checkField(className: String, fieldName: String) { if (classes.findField(className, fieldName) == null) { - throw UnknownApiException("Unknown field $className.$fieldName") + log.w("Unknown field $className.$fieldName") } } - private fun ensureMethodExists( + private fun checkMethod( className: String, methodName: String, descriptor: String ) { if (classes.findMethod(className, methodName, descriptor) == null) { - throw UnknownApiException("Unknown method $className.$methodName$descriptor") + log.w("Unknown method $className.$methodName$descriptor") } } fun setPolicyForClass(className: String, policy: FilterPolicyWithReason) { - ensureClassExists(className) + checkClass(className) mPolicies[getClassKey(className)] = policy } @@ -81,7 +81,7 @@ class InMemoryOutputFilter( } fun setPolicyForField(className: String, fieldName: String, policy: FilterPolicyWithReason) { - ensureFieldExists(className, fieldName) + checkField(className, fieldName) mPolicies[getFieldKey(className, fieldName)] = policy } @@ -100,7 +100,7 @@ class InMemoryOutputFilter( descriptor: String, policy: FilterPolicyWithReason, ) { - ensureMethodExists(className, methodName, descriptor) + checkMethod(className, methodName, descriptor) mPolicies[getMethodKey(className, methodName, descriptor)] = policy } @@ -110,8 +110,8 @@ class InMemoryOutputFilter( } fun setRenameTo(className: String, methodName: String, descriptor: String, toName: String) { - ensureMethodExists(className, methodName, descriptor) - ensureMethodExists(className, toName, descriptor) + checkMethod(className, methodName, descriptor) + checkMethod(className, toName, descriptor) mRenames[getMethodKey(className, methodName, descriptor)] = toName } @@ -121,7 +121,7 @@ class InMemoryOutputFilter( } fun setNativeSubstitutionClass(from: String, to: String) { - ensureClassExists(from) + checkClass(from) // Native substitute classes may be provided from other jars, so we can't do this check. // ensureClassExists(to) diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index 75b5fc8f77ea..c5acd81f1cf2 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -17,6 +17,7 @@ package com.android.hoststubgen.filters import com.android.hoststubgen.ParseException import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.toHumanReadableClassName import com.android.hoststubgen.log import com.android.hoststubgen.normalizeTextLine import com.android.hoststubgen.whitespaceRegex @@ -31,13 +32,17 @@ import java.util.Objects * Print a class node as a "keep" policy. */ fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) { - pw.printf("class %s\t%s\n", cn.name, "keep") + pw.printf("class %s %s\n", cn.name.toHumanReadableClassName(), "keep") - for (f in cn.fields ?: emptyList()) { - pw.printf(" field %s\t%s\n", f.name, "keep") + cn.fields?.let { + for (f in it.sortedWith(compareBy({ it.name }))) { + pw.printf(" field %s %s\n", f.name, "keep") + } } - for (m in cn.methods ?: emptyList()) { - pw.printf(" method %s\t%s\t%s\n", m.name, m.desc, "keep") + cn.methods?.let { + for (m in it.sortedWith(compareBy({ it.name }, { it.desc }))) { + pw.printf(" method %s %s %s\n", m.name, m.desc, "keep") + } } } @@ -66,6 +71,7 @@ fun createFilterFromTextPolicyFile( var aidlPolicy: FilterPolicyWithReason? = null var featureFlagsPolicy: FilterPolicyWithReason? = null var syspropsPolicy: FilterPolicyWithReason? = null + var rFilePolicy: FilterPolicyWithReason? = null try { BufferedReader(FileReader(filename)).use { reader -> @@ -162,6 +168,14 @@ fun createFilterFromTextPolicyFile( syspropsPolicy = policy.withReason( "$FILTER_REASON (special-class sysprops)") } + SpecialClass.RFile -> { + if (rFilePolicy != null) { + throw ParseException( + "Policy for R file already defined") + } + rFilePolicy = policy.withReason( + "$FILTER_REASON (special-class R file)") + } } } } @@ -225,13 +239,9 @@ fun createFilterFromTextPolicyFile( throw e.withSourceInfo(filename, lineNo) } - var ret: OutputFilter = imf - if (aidlPolicy != null || featureFlagsPolicy != null || syspropsPolicy != null) { - log.d("AndroidHeuristicsFilter enabled") - ret = AndroidHeuristicsFilter( - classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, imf) - } - return ret + // Wrap the in-memory-filter with AHF. + return AndroidHeuristicsFilter( + classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf) } } @@ -240,6 +250,7 @@ private enum class SpecialClass { Aidl, FeatureFlags, Sysprops, + RFile, } private fun resolveSpecialClass(className: String): SpecialClass { @@ -250,6 +261,7 @@ private fun resolveSpecialClass(className: String): SpecialClass { ":aidl" -> return SpecialClass.Aidl ":feature_flags" -> return SpecialClass.FeatureFlags ":sysprops" -> return SpecialClass.Sysprops + ":r" -> return SpecialClass.RFile } throw ParseException("Invalid special class name \"$className\"") } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt index 6643492a1394..c99ff0e5d990 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt @@ -195,6 +195,8 @@ abstract class BaseAdapter ( return null } + var newAccess = access + // Maybe rename the method. val newName: String val renameTo = filter.getRenameTo(currentClassName, name, descriptor) @@ -205,8 +207,9 @@ abstract class BaseAdapter ( // (the one with the @substitute/replace annotation). // `name` is the name of the method we're currently visiting, so it's usually a // "...$ravewnwood" name. - if (!checkSubstitutionMethodCompatibility( - classes, currentClassName, newName, name, descriptor, options.errors)) { + newAccess = checkSubstitutionMethodCompatibility( + classes, currentClassName, newName, name, descriptor, options.errors) + if (newAccess == NOT_COMPATIBLE) { return null } @@ -221,7 +224,7 @@ abstract class BaseAdapter ( // But note, we only use it when calling the super's method, // but not for visitMethodInner(), because when subclass wants to change access, // it can do so inside visitMethodInner(). - val newAccess = updateAccessFlags(access, name, descriptor) + newAccess = updateAccessFlags(newAccess, name, descriptor) val ret = visitMethodInner(access, newName, descriptor, signature, exceptions, policy, renameTo != null, @@ -303,4 +306,4 @@ abstract class BaseAdapter ( return ret } } -}
\ No newline at end of file +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt index 9d66c32e76ee..dc4f26bdda34 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt @@ -17,12 +17,19 @@ package com.android.hoststubgen.visitors import com.android.hoststubgen.HostStubGenErrors import com.android.hoststubgen.asm.ClassNodes +import com.android.hoststubgen.asm.clearVisibility import com.android.hoststubgen.asm.getVisibility import com.android.hoststubgen.asm.isStatic +const val NOT_COMPATIBLE: Int = -1 + /** * Make sure substitution from and to methods have matching definition. - * (static-ness, visibility.) + * (static-ness, etc) + * + * If the methods are compatible, return the "merged" [access] of the new method. + * + * If they are not compatible, returns [NOT_COMPATIBLE] */ fun checkSubstitutionMethodCompatibility( classes: ClassNodes, @@ -31,33 +38,31 @@ fun checkSubstitutionMethodCompatibility( toMethodName: String, // the one with either a "_host" or "$ravenwood" prefix. (typically) descriptor: String, errors: HostStubGenErrors, -): Boolean { +): Int { val from = classes.findMethod(className, fromMethodName, descriptor) if (from == null) { errors.onErrorFound( - "Substitution-from method not found: $className.$fromMethodName$descriptor") - return false + "Substitution-from method not found: $className.$fromMethodName$descriptor" + ) + return NOT_COMPATIBLE } val to = classes.findMethod(className, toMethodName, descriptor) if (to == null) { // This shouldn't happen, because the visitor visited this method... errors.onErrorFound( - "Substitution-to method not found: $className.$toMethodName$descriptor") - return false + "Substitution-to method not found: $className.$toMethodName$descriptor" + ) + return NOT_COMPATIBLE } if (from.isStatic() != to.isStatic()) { errors.onErrorFound( "Substitution method must have matching static-ness: " + - "$className.$fromMethodName$descriptor") - return false - } - if (from.getVisibility().ordinal > to.getVisibility().ordinal) { - errors.onErrorFound( - "Substitution method cannot have smaller visibility than original: " + - "$className.$fromMethodName$descriptor") - return false + "$className.$fromMethodName$descriptor" + ) + return NOT_COMPATIBLE } - return true + // Return the substitution's access flag but with the original method's visibility. + return clearVisibility (to.access) or getVisibility(from.access) } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index fa8fe6cd384f..dd638925a5bc 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -322,6 +322,78 @@ NestMembers: InnerClasses: public static #x= #x of #x; // Stub=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub of class com/android/hoststubgen/test/tinyframework/IPretendingAidl public static #x= #x of #x; // Proxy=class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy of class com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 3 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested; + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: iconst_1 + x: newarray int + x: dup + x: iconst_0 + x: iconst_1 + x: iastore + x: putstatic #x // Field ARRAY:[I + x: return + LineNumberTable: +} +SourceFile: "R.java" +NestHost: class com/android/hoststubgen/test/tinyframework/R +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 3 + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R; +} +SourceFile: "R.java" +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl @@ -572,9 +644,9 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnota suffix="_host" ) - public static int nativeAddThree_host(int); + private static int nativeAddThree_host(int); descriptor: (I)I - flags: (0x0009) ACC_PUBLIC, ACC_STATIC + flags: (0x000a) ACC_PRIVATE, ACC_STATIC Code: stack=2, locals=1, args_size=1 x: iload_0 diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index c605f767f527..906a81cf45e3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -122,6 +122,100 @@ RuntimeVisibleAnnotations: NestMembers: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 4 + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt index 11d5939b7917..10bc91da2544 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt @@ -348,6 +348,108 @@ RuntimeVisibleAnnotations: NestMembers: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: iconst_1 + x: newarray int + x: dup + x: iconst_0 + x: iconst_1 + x: iastore + x: putstatic #x // Field ARRAY:[I + x: return + LineNumberTable: + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 4 + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=1, locals=1, args_size=1 + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 this Lcom/android/hoststubgen/test/tinyframework/R; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index c605f767f527..906a81cf45e3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -122,6 +122,100 @@ RuntimeVisibleAnnotations: NestMembers: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=3, locals=0, args_size=0 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 1, attributes: 4 + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=3, locals=1, args_size=1 + x: new #x // class java/lang/RuntimeException + x: dup + x: ldc #x // String Stub! + x: invokespecial #x // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V + x: athrow + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt index 088bc80e11c5..fcf9a8c663ad 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt @@ -481,6 +481,136 @@ RuntimeVisibleAnnotations: NestMembers: com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub com/android/hoststubgen/test/tinyframework/IPretendingAidl$Stub$Proxy +## Class: com/android/hoststubgen/test/tinyframework/R$Nested.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R$Nested + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R$Nested + super_class: #x // java/lang/Object + interfaces: 0, fields: 1, methods: 2, attributes: 4 + public static int[] ARRAY; + descriptor: [I + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public com.android.hoststubgen.test.tinyframework.R$Nested(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/R$Nested; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + static {}; + descriptor: ()V + flags: (0x0008) ACC_STATIC + Code: + stack=4, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested + x: ldc #x // String <clinit> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R$Nested + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: iconst_1 + x: newarray int + x: dup + x: iconst_0 + x: iconst_1 + x: iastore + x: putstatic #x // Field ARRAY:[I + x: return + LineNumberTable: + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestHost: class com/android/hoststubgen/test/tinyframework/R +## Class: com/android/hoststubgen/test/tinyframework/R.class + Compiled from "R.java" +public class com.android.hoststubgen.test.tinyframework.R + minor version: 0 + major version: 61 + flags: (0x0021) ACC_PUBLIC, ACC_SUPER + this_class: #x // com/android/hoststubgen/test/tinyframework/R + super_class: #x // java/lang/Object + interfaces: 0, fields: 0, methods: 2, attributes: 4 + private static {}; + descriptor: ()V + flags: (0x000a) ACC_PRIVATE, ACC_STATIC + Code: + stack=2, locals=0, args_size=0 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V + x: return + + public com.android.hoststubgen.test.tinyframework.R(); + descriptor: ()V + flags: (0x0001) ACC_PUBLIC + Code: + stack=4, locals=1, args_size=1 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/R + x: ldc #x // String <init> + x: ldc #x // String ()V + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: aload_0 + x: invokespecial #x // Method java/lang/Object."<init>":()V + x: return + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 11 5 0 this Lcom/android/hoststubgen/test/tinyframework/R; + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +} +InnerClasses: + public static #x= #x of #x; // Nested=class com/android/hoststubgen/test/tinyframework/R$Nested of class com/android/hoststubgen/test/tinyframework/R +SourceFile: "R.java" +RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl +NestMembers: + com/android/hoststubgen/test/tinyframework/R$Nested ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class Compiled from "TinyFrameworkCallerCheck.java" class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt index d30208452a40..696b6d009dc2 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt @@ -19,6 +19,9 @@ class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy ~com # Heuristics rule: Stub all the AIDL classes. class :aidl stubclass +# Heuristics rule: Stub all the R classes. +class :r stubclass + # Default is "remove", so let's put all the base classes / interfaces in the stub first. class com.android.hoststubgen.test.tinyframework.subclasstest.C1 stub class com.android.hoststubgen.test.tinyframework.subclasstest.C2 stub diff --git a/core/java/android/app/StatusBarManager.aidl b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java index 687678c4ea8d..b1bedf4b6853 100644 --- a/core/java/android/app/StatusBarManager.aidl +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java @@ -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, @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.android.hoststubgen.test.tinyframework; -package android.app; - -parcelable StatusBarManager.DisableInfo;
\ No newline at end of file +public class R { + public static class Nested { + public static int[] ARRAY = new int[] {1}; + } +} diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java index ab387e0938c3..6d8a48a37fea 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java @@ -73,7 +73,8 @@ public class TinyFrameworkClassAnnotations { @HostSideTestSubstitute(suffix = "_host") public static native int nativeAddThree(int value); - public static int nativeAddThree_host(int value) { + // This method is private, but at runtime, it'll inherit the visibility of the original method + private static int nativeAddThree_host(int value) { return value + 3; } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index 762180dcf74b..37925e82bdb6 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -19,6 +19,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import com.android.hoststubgen.test.tinyframework.R.Nested; import com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.SubClass; import org.junit.Rule; @@ -328,4 +329,9 @@ public class TinyFrameworkClassTest { assertThat(IPretendingAidl.Stub.addOne(1)).isEqualTo(2); assertThat(IPretendingAidl.Stub.Proxy.addTwo(1)).isEqualTo(3); } + + @Test + public void testRFileHeuristics() { + assertThat(Nested.ARRAY.length).isEqualTo(1); + } } diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt index 0ea90ed2fbf0..75e2536a98fa 100644 --- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt +++ b/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt @@ -71,7 +71,7 @@ class HelperTest { addClass(cn) } - fun check(from: MethodNode?, to: MethodNode?, expected: Boolean) { + fun check(from: MethodNode?, to: MethodNode?, expected: Int) { assertThat(checkSubstitutionMethodCompatibility( classes, cn.name, @@ -82,21 +82,21 @@ class HelperTest { )).isEqualTo(expected) } - check(staticPublic, staticPublic, true) - check(staticPrivate, staticPrivate, true) - check(nonStaticPublic, nonStaticPublic, true) - check(nonStaticPProtected, nonStaticPProtected, true) + check(staticPublic, staticPublic, Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC) + check(staticPrivate, staticPrivate, Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC) + check(nonStaticPublic, nonStaticPublic, Opcodes.ACC_PUBLIC) + check(nonStaticPProtected, nonStaticPProtected, 0) - check(staticPublic, null, false) - check(null, staticPublic, false) + check(staticPublic, null, NOT_COMPATIBLE) + check(null, staticPublic, NOT_COMPATIBLE) - check(staticPublic, nonStaticPublic, false) - check(nonStaticPublic, staticPublic, false) + check(staticPublic, nonStaticPublic, NOT_COMPATIBLE) + check(nonStaticPublic, staticPublic, NOT_COMPATIBLE) - check(staticPublic, staticPrivate, false) - check(staticPrivate, staticPublic, true) + check(staticPublic, staticPrivate, Opcodes.ACC_PUBLIC or Opcodes.ACC_STATIC) + check(staticPrivate, staticPublic, Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC) - check(nonStaticPublic, nonStaticPProtected, false) - check(nonStaticPProtected, nonStaticPublic, true) + check(nonStaticPublic, nonStaticPProtected, Opcodes.ACC_PUBLIC) + check(nonStaticPProtected, nonStaticPublic, 0) } }
\ No newline at end of file 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; } diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig index 3c734bc7e1e3..c5bc0396de34 100644 --- a/wifi/wifi.aconfig +++ b/wifi/wifi.aconfig @@ -9,3 +9,11 @@ flag { bug: "313038031" is_fixed_read_only: true } + +flag { + name: "network_provider_battery_charging_status" + is_exported: true + namespace: "wifi" + description: "Control the API that allows setting / reading the NetworkProviderInfo's battery charging status" + bug: "305067231" +} |